【VBA】ファイル・CSV操作完全ガイド|出力・文字コード・フォルダ操作をまとめて解説

VBAでテストを回していた頃、結果を手作業でコピーして残すのが面倒で、必ずCSVに書き出してバックアップするようにしていた。

手作業だとうっかり上書きしてしまうこともある。だから「実行したら結果が自動でCSVになり、日付付きのフォルダに残る」という状態を作っておくと安心できる。

この記事では、シートのCSV出力から文字コードの注意点、フォルダ操作、それらを組み合わせたバックアップマクロまで、実務でそのまま使える形でまとめました。

シート全体をCSV出力する(基本)

まずは最もシンプルな方法として、アクティブシート全体をCSV出力するコードです。

VB
Public Sub ExportSheetToCSV()
    Dim ws As Worksheet
    Dim outputPath As String
    Dim fileNum As Integer
    Dim i As Long, j As Long
    Dim lastRow As Long, lastCol As Long
    Dim outputLine As String
    Dim cellValue As String

    Set ws = ThisWorkbook.ActiveSheet

    ' 最終行・最終列を取得
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column

    ' 出力先パスを設定(Excelファイルと同じフォルダ)
    outputPath = ThisWorkbook.Path & "\output.csv"

    fileNum = FreeFile
    Open outputPath For Output As #fileNum

    For i = 1 To lastRow
        outputLine = ""
        For j = 1 To lastCol
            cellValue = CStr(ws.Cells(i, j).Value)

            ' カンマ・ダブルクォートを含む場合はダブルクォートで囲む
            If InStr(cellValue, ",") > 0 Or InStr(cellValue, """") > 0 Then
                cellValue = """" & Replace(cellValue, """", """""") & """"
            End If

            outputLine = outputLine & cellValue
            If j < lastCol Then outputLine = outputLine & ","
        Next j
        Print #fileNum, outputLine
    Next i

    Close #fileNum
    MsgBox "CSV出力が完了しました。", vbInformation
End Sub
  • ActiveSheetを対象にしているので、どのシートでも使いまわせる
  • End(xlUp) / End(xlToLeft)でデータのある最終行・最終列を自動取得
  • カンマやダブルクォートを含むセルは自動でダブルクォートで囲む(CSV形式のルール)

ただし、この書き方はセルを1つずつ読み書きしているので、データが数千行を超えたあたりから明らかに遅くなる。

範囲指定でCSV出力する(配列で高速化)

大量データを扱うなら、セルを1つずつ読み書きするのではなく、範囲を配列にまとめて取得してから書き出すほうが圧倒的に速い。

以下は、UsedRange(シートの使用中セル範囲)を配列で取得し、CSV用のエスケープ処理を関数に分けて整理したコードです。

VB
Option Explicit

'===============================
' メイン処理
'===============================
Public Sub ExportUsedRangeToCSV()
    On Error GoTo ErrorHandler

    Dim dataArray As Variant
    Dim outputPath As String
    Dim fileNum As Integer
    Dim i As Long

    '--- 対象データを取得 ---
    dataArray = GetSheetData("Sheet1")

    '--- 出力ファイルパス ---
    outputPath = ThisWorkbook.Path & "\output.csv"

    '--- ファイルを開く ---
    fileNum = FreeFile
    Open outputPath For Output As #fileNum

    '--- CSV形式で書き込み ---
    For i = LBound(dataArray, 1) To UBound(dataArray, 1)
        Print #fileNum, ArrayRowToCSVLine(dataArray, i)
    Next i

    '--- ファイルを閉じる ---
    Close #fileNum

    MsgBox "CSVファイルを出力しました。", vbInformation
    Exit Sub

'--- エラー処理 ---
ErrorHandler:
    MsgBox GetErrorMessage(Err.Number, Err.Description), vbCritical
    On Error Resume Next
    Close #fileNum
End Sub


'===============================
' シートから使用中範囲を配列取得
'===============================
Private Function GetSheetData(sheetName As String) As Variant
    Dim ws As Worksheet
    Set ws = ThisWorkbook.Worksheets(sheetName)
    GetSheetData = ws.UsedRange.Value
End Function


'===============================
' 1セル分のCSV用エスケープ処理
'===============================
Private Function EscapeCSVValue(value As Variant) As String
    Dim cellValue As String
    cellValue = CStr(value)

    ' 囲むべき条件:カンマ、ダブルクォート、改行を含む場合
    If InStr(cellValue, ",") > 0 _
       Or InStr(cellValue, """") > 0 _
       Or InStr(cellValue, vbCr) > 0 _
       Or InStr(cellValue, vbLf) > 0 Then

        cellValue = """" & Replace(cellValue, """", """""") & """"
    End If

    EscapeCSVValue = cellValue
End Function


'===============================
' 配列の1行をCSV文字列に変換
'===============================
Private Function ArrayRowToCSVLine(dataArray As Variant, rowIndex As Long) As String
    Dim j As Long, line As String
    For j = LBound(dataArray, 2) To UBound(dataArray, 2)
        line = line & EscapeCSVValue(dataArray(rowIndex, j))
        If j < UBound(dataArray, 2) Then line = line & ","
    Next j
    ArrayRowToCSVLine = line
End Function


'===============================
' エラーメッセージ生成
'===============================
Private Function GetErrorMessage(errNum As Long, errDesc As String) As String
    Select Case errNum
        Case 70: GetErrorMessage = "アクセス権限がありません。ファイルが開かれていないか確認してください。"
        Case 75: GetErrorMessage = "ファイルパスが無効です。"
        Case 76: GetErrorMessage = "指定されたパスが見つかりません。"
        Case 6:  GetErrorMessage = "ファイルサイズが大きすぎます。"
        Case Else
            GetErrorMessage = "予期せぬエラーが発生しました。" & vbCrLf & _
                               "番号: " & errNum & vbCrLf & _
                               "説明: " & errDesc
    End Select
End Function

関数を分けているのは、あとで「日付フォルダにバックアップする」マクロに組み込むときに、GetSheetDataArrayRowToCSVLineをそのまま呼び出せるようにするためです。

文字コード(Shift-JIS / UTF-8)に気をつける

ここは実務でいちばん引っかかりやすいポイントです。VBAの標準的なファイル出力(Open ... For Output)は、Windowsのデフォルト文字コードであるShift-JISでCSVを作成します。

Excelで開くだけなら問題ありませんが、僕は以前、このCSVをそのままPython側の別システムに渡したところ、文字化けして読み込めなかったことがあります。他システムに連携する場合はUTF-8が求められることが多いので、出力先に応じて使い分ける必要があります。

Shift-JISで出力する(デフォルト)

Open outputPath For Output As #fileNumの標準コードがそのままShift-JIS出力になります。追加の設定は不要です。

UTF-8で出力する(ADODB.Streamを使う)

UTF-8で出力したい場合はADODB.Streamを使います。

VB
Public Sub ExportToCSV_UTF8()
    Dim ws As Worksheet
    Dim stream As Object
    Dim i As Long, j As Long
    Dim lastRow As Long, lastCol As Long
    Dim outputLine As String
    Dim cellValue As String
    Dim outputPath As String

    Set ws = ThisWorkbook.ActiveSheet
    lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
    lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
    outputPath = ThisWorkbook.Path & "\output_utf8.csv"

    ' ADODB.Streamを使ってUTF-8で書き込む
    Set stream = CreateObject("ADODB.Stream")
    stream.Type = 2           ' テキストモード
    stream.Charset = "UTF-8"
    stream.Open

    For i = 1 To lastRow
        outputLine = ""
        For j = 1 To lastCol
            cellValue = CStr(ws.Cells(i, j).Value)
            If InStr(cellValue, ",") > 0 Or InStr(cellValue, """") > 0 Then
                cellValue = """" & Replace(cellValue, """", """""") & """"
            End If
            outputLine = outputLine & cellValue
            If j < lastCol Then outputLine = outputLine & ","
        Next j
        stream.WriteText outputLine & vbCrLf
    Next i

    stream.SaveToFile outputPath, 2  ' 2 = 上書き保存
    stream.Close

    MsgBox "UTF-8でCSV出力しました。", vbInformation
End Sub
  • stream.Charset = "UTF-8"で文字コードを指定
  • BOM(バイト順マーク)なしのUTF-8で出力される
  • Excelで直接開くと文字化けする場合があるが、テキストエディタやPythonでは正常に読める

フォルダを作成・選択する

フォルダを作成する

VB
Sub CreateFolder()
    Dim folderPath As String
    folderPath = "C:\Users\YourName\Desktop\TestFolder"

    If Dir(folderPath, vbDirectory) = "" Then
        MkDir folderPath
        MsgBox "フォルダを作成しました!"
    Else
        MsgBox "すでにフォルダがあります!"
    End If
End Sub

フォルダがなければ作成、あればメッセージを表示するだけのシンプルな作りです。

フォルダ選択ダイアログを出す

保存先を毎回固定パスにしたくない場合は、ユーザーにフォルダを選ばせるダイアログを出します。

VB
Function SelectFolder() As String
    Dim folderDialog As FileDialog
    Set folderDialog = Application.FileDialog(msoFileDialogFolderPicker)

    If folderDialog.Show = -1 Then
        SelectFolder = folderDialog.SelectedItems(1)
    Else
        SelectFolder = ""
    End If
End Function

キャンセルされた場合は空文字を返すようにしているので、呼び出し側で必ず判定してください。

VB
Sub TestSelectFolder()
    Dim path As String
    path = SelectFolder()
    If path = "" Then Exit Sub
    MsgBox "選択されたフォルダは:" & path
End Sub

フォルダ内のファイル名を一括取得する

フォルダの中に何が入っているか一覧化したいときはDir関数をループで使います。

VB
Sub GetFileNamesInFolder()
    Dim folderPath As String
    Dim fileName As String
    Dim i As Long

    folderPath = SelectFolder() & "\"
    If folderPath = "\" Then Exit Sub

    fileName = Dir(folderPath & "*.*")
    i = 1

    Do While fileName <> ""
        Cells(i, 1).Value = fileName
        fileName = Dir
        i = i + 1
    Loop
End Sub

選択したフォルダ内のすべてのファイル名を、ExcelのA列にズラッと表示します。

実務での使い分けを体感する:テスト結果を日付フォルダにバックアップする

ここまでのCSV出力とフォルダ操作を組み合わせると、「テストを実行したら、結果が自動でCSVになり、日付付きのフォルダに残る」というマクロが作れます。

僕がテスト結果をエビデンスとして残すときによく使っている形です。

VB
Option Explicit

'===============================
' テスト結果を日付フォルダにバックアップして開く
'===============================
Public Sub BackupResultsToDateFolder()
    On Error GoTo ErrorHandler

    Dim basePath As String
    Dim newFolder As String
    Dim dataArray As Variant
    Dim outputPath As String
    Dim fileNum As Integer
    Dim i As Long

    '--- バックアップ先の親フォルダを選択 ---
    basePath = SelectFolder()
    If basePath = "" Then Exit Sub

    '--- 日付フォルダを作成 ---
    newFolder = basePath & "\" & Format(Now, "yyyymmdd_hhnnss")
    If Dir(newFolder, vbDirectory) = "" Then
        MkDir newFolder
    End If

    '--- テスト結果(Sheet1のUsedRange)をCSVに書き出す ---
    dataArray = GetSheetData("Sheet1")
    outputPath = newFolder & "\result.csv"

    fileNum = FreeFile
    Open outputPath For Output As #fileNum
    For i = LBound(dataArray, 1) To UBound(dataArray, 1)
        Print #fileNum, ArrayRowToCSVLine(dataArray, i)
    Next i
    Close #fileNum

    '--- 保存したフォルダをそのまま開く ---
    Shell "explorer.exe """ & newFolder & """", vbNormalFocus
    Exit Sub

ErrorHandler:
    MsgBox GetErrorMessage(Err.Number, Err.Description), vbCritical
    On Error Resume Next
    Close #fileNum
End Sub

GetSheetDataArrayRowToCSVLineGetErrorMessageは「範囲指定でCSV出力する」の章で紹介した関数をそのまま使っています。CSV出力・フォルダ作成・フォルダを開く、という3つの操作を1つのマクロにまとめただけで、バックアップ作業がボタン1つで終わるようになります。

よくあるエラーと対処

ファイル・フォルダ操作は、コードが正しくても環境側の要因でエラーになることがあります。よく出るエラー番号と原因をまとめました。

エラー番号原因対処
70アクセス権限がないファイルが他のプログラムで開かれていないか確認する
75ファイルパスが無効パスの長さ・使用できない文字を確認する
76指定したパスが見つからない保存先フォルダが存在するか確認する(先にMkDirする)
6ファイルサイズが大きすぎるデータ量を減らすか、分割して保存する

意外と見落としがちですが、Shell "explorer.exe"でパスを渡すときは、パスをダブルクォートで囲む(""" & path & """")のを忘れるとフォルダ名にスペースが含まれた瞬間に動かなくなります。

よくある質問

本文で触れきれなかった細かい疑問をQ&Aでまとめておきます。

UTF-8で出力したCSVをExcelで開くと文字化けするのはなぜですか?

ADODB.Streamで出力したUTF-8はBOM(バイト順マーク)なしのため、Excelが文字コードを正しく判定できず文字化けすることがあります。Excelで直接開いて確認したい場合はShift-JIS出力を、他システムへの連携が目的ならUTF-8のまま渡して問題ありません。

セルを1つずつ書き込む方法と配列を使う方法、どのくらい速度が変わりますか?

数百行程度なら体感差はほぼありません。数千行を超えるあたりから、セル単位のアクセスを繰り返す書き方は明らかに遅くなります。データ件数が読めない場合は、最初から配列で読み込む書き方にしておくのが安全です。

フォルダ選択ダイアログでキャンセルするとどうなりますか?

SelectFolder関数は空文字を返します。呼び出し側でIf basePath = "" Then Exit Subのように判定していないと、空のパスに対してファイル操作を試みてエラー76(パスが見つからない)になります。

まとめ

CSV出力は「シート全体か範囲か」「Shift-JISかUTF-8か」で書き方が変わり、フォルダ操作は作成・選択・一覧取得・オープンの4つを組み合わせれば大抵の場面はカバーできます。

1つずつ覚えるよりも、この記事の最後のバックアップマクロのように組み合わせてしまったほうが、結局は実務で使い続けやすいです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

普段は主にPython開発をしています。
最近はAI駆動開発にも関わっています。

コメント

コメントする