【bat】CSVを読み取って業務処理を自動化する完全ガイド|コピー・削除・DB登録・フィルタ・変換・集計・メール送信まで実践パターン徹底解説

【bat】CSVを読み取って業務処理を自動化する完全ガイド|コピー・削除・DB登録・フィルタ・変換・集計・メール送信まで実践パターン徹底解説 bat

CSVファイルは業務データの共通フォーマットです。顧客リスト・注文データ・ファイル配布リスト・設定値一覧——これらをExcelで開いて手作業で処理するのではなく、バッチファイルで自動化することで、数百件・数千件の処理を一瞬で完了させられます。

バッチファイルでCSVを扱うポイントは、読み込むこと読んだデータで何をするかを分けて考えることです。読み込みの基本構文は共通で、「何をするか」の部分に業務ロジックを差し込むだけで多様な自動化が実現できます。

この記事では、バッチファイルでCSVファイルを読み込む方法完全ガイドで解説している読み込み基本構文をベースに、ファイル操作・条件フィルタリング・値変換・集計・DB登録・メール送信まで、業務で直接使えるパターンを体系的に解説します。

この記事で学べること
for /f の tokens/delims/skip でCSVを正確に読み込む基本構文の復習、CSVに記載されたパスでファイルを一括コピー・移動・削除・リネームする方法、列の値で条件分岐・数値範囲フィルタ・キーワードフィルタリングする方法、CSV列の並び替え・値変換・計算列追加など加工して別CSVを出力する方法、sqlcmdでCSVデータをDBに一括登録するSQL生成パターン、ダブルクォートで囲まれたCSV・カンマ含む値への対処法(PowerShell Import-Csv)、集計(件数・合計・最大最小)をCSVから算出する方法
スポンサーリンク

CSV読み込みの基本構文(復習)

詳細はバッチファイルでCSVファイルを読み込む方法完全ガイドで解説しています。ここでは実践パターンを使う前提として最低限の構文を確認します。

input.csv(サンプルデータ)
氏名,メールアドレス,部署,月給
田中一郎,tanaka@example.com,営業部,350000
山田花子,yamada@example.com,開発部,420000
鈴木次郎,suzuki@example.com,総務部,310000
佐藤三郎,sato@example.com,営業部,380000
for /f でCSVを1行ずつ読み込む基本形
@echo off
setlocal enabledelayedexpansion

:: skip=1 でヘッダー行をスキップ
:: tokens=1,2,3,4 で4列すべてを取得
:: delims=, でカンマ区切り
for /f "usebackq skip=1 tokens=1,2,3,4 delims=," %%A in ("input.csv") do (
    set "NAME=%%A"
    set "MAIL=%%B"
    set "DEPT=%%C"
    set "SALARY=%%D"

    echo 名前: !NAME! / 部署: !DEPT! / 給与: !SALARY!
)

endlocal
オプション 意味
tokens=1,2,3 取得する列番号(%%Aから順に割り当て) tokens=1,3 → 1列目と3列目だけ取得
tokens=1-4 1〜4列目を連続で取得 tokens=1,2,3,4 と同じ
tokens=1* 1列目を%%A、残り全部を%%Bに取得 スペース含む値や列数不定時に有効
delims=, 区切り文字をカンマに設定 TSVなら delims=\t(タブ)
skip=1 先頭N行をスキップ ヘッダー行のあるCSVに必須
usebackq ダブルクォートでファイルパスを囲める パスにスペースが含まれる場合に必須
for /f はダブルクォートで囲まれた値を正しく処理できない
"田中,一郎","tanaka@example.com" のようにRFC 4180準拠でクォートされたCSVは、for /f では正しく解析できません(クォートを無視してカンマで分割してしまいます)。この場合は後述のPowerShell Import-Csv の使用を検討してください。

パターン1:CSVに記載されたパスでファイルを一括操作する

CSVの各行に「コピー元」「コピー先」などのパスを記載し、バッチでそれを読み取って処理する方法です。ファイルの一括配布・整理・削除など定型作業の自動化に最も多く使われるパターンです。バッチファイルでファイルをコピーする方法完全ガイドのコピー方法と組み合わせると応用の幅が広がります。

ファイルの一括コピー

copy_list.csv
コピー元,コピー先
C:\reports\jan_report.pdf,\\fileserver\archive\2024\01\
C:\reports\feb_report.pdf,\\fileserver\archive\2024\02\
C:\reports\mar_report.pdf,\\fileserver\archive\2024\03\
CSVのパスに基づいてファイルを一括コピー
@echo off
setlocal enabledelayedexpansion

set "CSV=copy_list.csv"
set "LOG=copy_log_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.txt"
set /a OK=0
set /a FAIL=0

echo 開始: %DATE% %TIME% > "%LOG%"

for /f "usebackq skip=1 tokens=1,2 delims=," %%A in ("%CSV%") do (
    set "SRC=%%A"
    set "DST=%%B"

    if not exist "!SRC!" (
        echo [SKIP] コピー元が見つかりません: !SRC! >> "%LOG%"
        echo [SKIP] !SRC!
        set /a FAIL+=1
    ) else (
        :: コピー先フォルダが存在しない場合は作成
        if not exist "!DST!" mkdir "!DST!" 2>nul

        copy /y "!SRC!" "!DST!" > nul 2>&1
        if !ERRORLEVEL! EQU 0 (
            echo [OK]   !SRC! → !DST! >> "%LOG%"
            echo [OK]   !SRC!
            set /a OK+=1
        ) else (
            echo [FAIL] !SRC! → !DST! >> "%LOG%"
            echo [FAIL] !SRC!
            set /a FAIL+=1
        )
    )
)

echo 完了: 成功 %OK% / 失敗 %FAIL% >> "%LOG%"
echo.
echo 完了: 成功 %OK% / 失敗 %FAIL%
if !FAIL! GTR 0 exit /b 1
endlocal
exit /b 0

CSVに基づいてファイルをリネーム

rename_list.csv(旧ファイル名,新ファイル名)
旧ファイル名,新ファイル名
C:\photos\IMG_0001.jpg,C:\photos\2024-01-01_会議.jpg
C:\photos\IMG_0002.jpg,C:\photos\2024-01-01_懇親会.jpg
C:\photos\IMG_0003.jpg,C:\photos\2024-01-02_現場視察.jpg
CSVのリストに従ってファイルを一括リネーム
@echo off
setlocal enabledelayedexpansion

set "CSV=rename_list.csv"
set /a OK=0
set /a FAIL=0

for /f "usebackq skip=1 tokens=1,2 delims=," %%A in ("%CSV%") do (
    set "OLD=%%A"
    set "NEW=%%B"

    if not exist "!OLD!" (
        echo [SKIP] ファイルが見つかりません: !OLD!
        set /a FAIL+=1
    ) else (
        ren "!OLD!" "!NEW!" 2>nul

        :: ren はフルパスでの移動ができないため move で代用
        if !ERRORLEVEL! NEQ 0 (
            move /y "!OLD!" "!NEW!" > nul 2>&1
        )
        if !ERRORLEVEL! EQU 0 (
            echo [OK]   !OLD! → !NEW!
            set /a OK+=1
        ) else (
            echo [FAIL] !OLD!
            set /a FAIL+=1
        )
    )
)

echo 完了: 成功 %OK% / 失敗 %FAIL%
endlocal

パターン2:CSVデータの条件フィルタリング・行の抽出

読み込んだCSVの値を条件判定して、特定の行だけ処理・抽出・別ファイルに出力するパターンです。バッチファイルで条件分岐する方法完全ガイドの条件分岐とバッチファイルで文字列を比較する方法完全ガイドの文字列比較を組み合わせることで、複雑な条件にも対応できます。

特定の列の値で行を振り分ける

部署ごとに別ファイルに振り分ける
@echo off
setlocal enabledelayedexpansion

set "CSV=employees.csv"
set "OUT_DIR=output"
if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"

:: 出力ファイルのヘッダーを書き込む
echo 氏名,メールアドレス,部署,月給 > "%OUT_DIR%\営業部.csv"
echo 氏名,メールアドレス,部署,月給 > "%OUT_DIR%\開発部.csv"
echo 氏名,メールアドレス,部署,月給 > "%OUT_DIR%\その他.csv"

for /f "usebackq skip=1 tokens=1,2,3,4 delims=," %%A in ("%CSV%") do (
    set "ROW=%%A,%%B,%%C,%%D"

    if /i "%%C"=="営業部" (
        echo !ROW! >> "%OUT_DIR%\営業部.csv"
    ) else if /i "%%C"=="開発部" (
        echo !ROW! >> "%OUT_DIR%\開発部.csv"
    ) else (
        echo !ROW! >> "%OUT_DIR%\その他.csv"
    )
)

echo 振り分け完了。%OUT_DIR%\ フォルダを確認してください。
endlocal

数値範囲で行をフィルタリングする

月給が350,000円以上の行だけ抽出
@echo off
setlocal enabledelayedexpansion

set "CSV=employees.csv"
set "OUT=high_salary.csv"

echo 氏名,メールアドレス,部署,月給 > "%OUT%"

for /f "usebackq skip=1 tokens=1,2,3,4 delims=," %%A in ("%CSV%") do (
    set /a SALARY=%%D

    :: 350000以上の行だけ出力
    if !SALARY! GEQ 350000 (
        echo %%A,%%B,%%C,%%D >> "%OUT%"
        echo [抽出] %%A - %%D円
    )
)

echo 完了: %OUT% に保存しました。
endlocal
set /a で数値比較する際の注意点
set /a SALARY=%%D では、値に数字以外(スペース・カンマ)が含まれるとエラーになります。値の整合性が不確かなCSVには事前にfindstrでバリデーションを入れてください。バッチファイルで計算する方法完全ガイドで数値計算の詳細な使い方を解説しています。

キーワードで行をフィルタリングする(findstr活用)

特定キーワードを含む行だけ抽出
@echo off
setlocal enabledelayedexpansion

set "CSV=orders.csv"
set "KEYWORD=キャンセル"
set "OUT=cancelled_orders.csv"

:: findstr でキーワードを含む行を一括抽出
:: /i: 大文字小文字無視  /g: ファイルからキーワード読み込みも可能
findstr /i "%KEYWORD%" "%CSV%" > "%OUT%"

echo 「%KEYWORD%」を含む行を %OUT% に抽出しました。

:: 抽出件数を確認
for /f %%C in ('find /c "" "%OUT%"') do set "COUNT=%%C"
echo 抽出件数: %COUNT% 件
endlocal
複数キーワードのいずれかを含む行を抽出
@echo off
setlocal

set "CSV=orders.csv"
set "OUT=target_orders.csv"

:: findstr の OR 検索(複数キーワードをスペース区切り)
findstr /i "キャンセル 返品 不良" "%CSV%" > "%OUT%"

echo 対象行を %OUT% に抽出しました。
endlocal

パターン3:CSVの列変換・加工・計算列追加

CSVを読み取りながら列の値を変換・計算し、別のCSVとして出力するパターンです。バッチファイルで文字列置換する方法完全ガイドの置換技術と組み合わせると、コード値の名称変換なども実現できます。

列の並び替えと一部列のみ抽出して出力

4列CSVから氏名とメールアドレスだけ取り出す
@echo off
setlocal

set "CSV=employees.csv"
set "OUT=mail_list.csv"

echo 氏名,メールアドレス > "%OUT%"

for /f "usebackq skip=1 tokens=1,2 delims=," %%A in ("%CSV%") do (
    echo %%A,%%B >> "%OUT%"
)

echo 完了: メールリストを %OUT% に出力しました。
endlocal

値の変換:コードを名称に置き換える

部署コードを部署名に変換して出力
@echo off
setlocal enabledelayedexpansion

:: コード→名称マッピング
set "DEPT_01=営業部"
set "DEPT_02=開発部"
set "DEPT_03=総務部"
set "DEPT_04=経理部"

set "CSV=staff_code.csv"
set "OUT=staff_name.csv"

echo 社員番号,氏名,部署名 > "%OUT%"

for /f "usebackq skip=1 tokens=1,2,3 delims=," %%A in ("%CSV%") do (
    set "EMP_NO=%%A"
    set "NAME=%%B"
    set "CODE=%%C"

    :: コードに対応する部署名を取得
    set "DEPT_NAME=!DEPT_%%C!"

    :: マッピングが見つからない場合は「不明」
    if not defined DEPT_NAME set "DEPT_NAME=不明(%CODE%)"

    echo !EMP_NO!,!NAME!,!DEPT_NAME! >> "%OUT%"
)

echo 完了: %OUT% に変換済みデータを出力しました。
endlocal

計算列を追加して出力する(税込価格・合計など)

単価と数量から金額・消費税・税込を計算して出力
@echo off
setlocal enabledelayedexpansion

set "CSV=order_items.csv"
set "OUT=order_calc.csv"
set /a TOTAL=0
set /a TAX_RATE=10

echo 商品名,単価,数量,金額,消費税,税込金額 > "%OUT%"

for /f "usebackq skip=1 tokens=1,2,3 delims=," %%A in ("%CSV%") do (
    set "ITEM=%%A"
    set /a PRICE=%%B
    set /a QTY=%%C

    set /a AMT=PRICE * QTY
    set /a TAX=AMT * TAX_RATE / 100
    set /a INCL=AMT + TAX
    set /a TOTAL+=INCL

    echo !ITEM!,!PRICE!,!QTY!,!AMT!,!TAX!,!INCL! >> "%OUT%"
    echo [計算] !ITEM!: !INCL!円(税込)
)

echo. >> "%OUT%"
echo 合計,,,,,%TOTAL% >> "%OUT%"
echo 合計金額(税込): %TOTAL%円
endlocal
set /a は整数演算のみ
set /a は整数のみ対応で、小数点は扱えません。消費税10%の計算では AMT * 10 / 100 のように整数演算で近似します。より精度が必要な計算はPowerShellに委ねてください: powershell -Command "[math]::Round(1234 * 1.1, 0)"

パターン4:CSVデータをDBに一括登録する(SQL文生成)

CSVの各行からINSERT文を生成し、sqlcmd(SQL Server)やmysqlコマンドで実行する方法です。バッチファイルでデータベースにSQLを実行する方法完全ガイドでDB接続の詳細パターンを解説しています。

CSVからINSERT文を生成してSQL Serverに実行

CSVからSQL Serverに一括INSERT
@echo off
setlocal enabledelayedexpansion

set "CSV=employees.csv"
set "SQL_FILE=%TEMP%\import_employees.sql"
set "SERVER=localhost"
set "DB=HRDatabase"

:: SQL文を生成
echo SET NOCOUNT ON; > "%SQL_FILE%"
echo BEGIN TRANSACTION; >> "%SQL_FILE%"

set /a INSERT_COUNT=0
set /a ERR_COUNT=0

for /f "usebackq skip=1 tokens=1,2,3,4 delims=," %%A in ("%CSV%") do (
    set "NAME=%%A"
    set "MAIL=%%B"
    set "DEPT=%%C"
    set /a SALARY=%%D

    :: シングルクォートのエスケープ(''に置換)
    set "NAME=!NAME:'=''!"
    set "MAIL=!MAIL:'=''!"
    set "DEPT=!DEPT:'=''!"

    echo INSERT INTO Employees (Name, Email, Department, Salary) >> "%SQL_FILE%"
    echo VALUES ('!NAME!', '!MAIL!', '!DEPT!', !SALARY!); >> "%SQL_FILE%"
    set /a INSERT_COUNT+=1
)

echo COMMIT; >> "%SQL_FILE%"

:: sqlcmdで実行
sqlcmd -S "%SERVER%" -d "%DB%" -i "%SQL_FILE%" -b
set /a SQL_ERR=!ERRORLEVEL!

if !SQL_ERR! EQU 0 (
    echo [OK] %INSERT_COUNT%件をインポートしました。
) else (
    echo [FAIL] SQLエラーが発生しました。ERRORLEVEL=!SQL_ERR!
    sqlcmd -S "%SERVER%" -d "%DB%" -Q "ROLLBACK"
)

del "%SQL_FILE%" 2>nul
endlocal
exit /b !SQL_ERR!
MySQLへの一括INSERT(ロードインフィル活用)
@echo off
setlocal enabledelayedexpansion

set "CSV=employees.csv"
set "SERVER=localhost"
set "DB=hr_db"
set "TABLE=employees"
set "USER=dbuser"
set /p PASS=MySQLパスワード:

:: MySQL の LOAD DATA LOCAL INFILE で高速一括インポート
mysql -h "%SERVER%" -u "%USER%" -p"%PASS%" "%DB%" ^
    -e "LOAD DATA LOCAL INFILE '%CSV%' INTO TABLE %TABLE% ^
        FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' ^
        LINES TERMINATED BY '\r\n' ^
        IGNORE 1 ROWS ^
        (name, email, department, salary);"

if !ERRORLEVEL! EQU 0 (
    echo [OK] インポート完了。
) else (
    echo [FAIL] MySQLインポートに失敗しました。
    exit /b 1
)
endlocal
LOAD DATA LOCAL INFILEの方が高速
MySQLではINSERT文を1件ずつ実行するよりも LOAD DATA LOCAL INFILE の方が数十倍速くCSVをインポートできます。数万件以上のデータを扱う場合は積極的に活用してください。ただし、MySQLの設定で local_infile=ON が必要です。

パターン5:ダブルクォートで囲まれたCSVへの対処

RFC 4180準拠のCSV(値をダブルクォートで囲む形式)は、for /f では正しく解析できません。このような場合はPowerShellの Import-Csv を使うか、前処理でクォートを除去するかの2択になります。

PowerShell Import-Csv で処理する(確実な方法)

quoted_data.csv(ダブルクォートあり)
"氏名","メールアドレス","備考"
"田中,一郎","tanaka@example.com","営業担当,関東エリア"
"山田 花子","yamada@example.com","開発リーダー"
バッチからPowerShellでダブルクォートCSVを処理
@echo off
setlocal enabledelayedexpansion

set "CSV=quoted_data.csv"
set "OUT=result.csv"

:: PowerShell Import-Csv で RFC4180対応のCSVを正しく読み込む
powershell -NoProfile -Command ^
    "Import-Csv '%CSV%' -Encoding UTF8 | ForEach-Object { ^
        $name = $_.氏名; ^
        $mail = $_.メールアドレス; ^
        $note = $_.備考; ^
        "$name,$mail,$note" ^
    }" > "%OUT%"

echo 処理完了: %OUT%
endlocal

バッチ側でダブルクォートを除去してから for /f で読む

ダブルクォートを前処理で除去してから読み込む
@echo off
setlocal enabledelayedexpansion

set "CSV=quoted_data.csv"
set "CLEAN=%TEMP%\clean_input.csv"

:: PowerShellでダブルクォートを除去した一時ファイルを作成
powershell -NoProfile -Command ^
    "(Get-Content '%CSV%' -Encoding UTF8) -replace '"','' | Set-Content '%CLEAN%' -Encoding UTF8"

:: クリーン済みファイルを for /f で読む
for /f "usebackq skip=1 tokens=1,2,3 delims=," %%A in ("%CLEAN%") do (
    echo 氏名: %%A
    echo メール: %%B
    echo 備考: %%C
    echo ---
)

del "%CLEAN%" 2>nul
endlocal
カンマを含む値はダブルクォート除去だけでは対処できない
"田中,一郎"」のように値自体にカンマが含まれるCSVは、ダブルクォートを除去しただけだと分割位置がずれます。この場合は必ず Import-Csv を使ってください。Import-Csv はカンマを含む値とダブルクォートエスケープ("")の両方を正しく処理します。

パターン6:CSVデータの集計(件数・合計・最大最小)

CSVを読みながら件数や合計を集計するパターンです。Excelを開かずにバッチで集計値を得られると、レポートの自動生成や閾値チェックに活用できます。

件数・合計・最大・最小・平均を一括集計
@echo off
setlocal enabledelayedexpansion

set "CSV=employees.csv"
set /a COUNT=0
set /a SUM=0
set /a MAX=0
set /a MIN=999999999

for /f "usebackq skip=1 tokens=1,2,3,4 delims=," %%A in ("%CSV%") do (
    set /a SALARY=%%D
    set /a COUNT+=1
    set /a SUM+=SALARY

    if !SALARY! GTR !MAX! set /a MAX=SALARY
    if !SALARY! LSS !MIN! set /a MIN=SALARY
)

:: 平均(整数除算)
if !COUNT! GTR 0 (
    set /a AVG=SUM / COUNT
) else (
    set /a AVG=0
)

echo ─────────────────────────
echo 集計結果:
echo   件数: %COUNT% 名
echo   合計: %SUM% 円
echo   平均: %AVG% 円
echo   最高: %MAX% 円
echo   最低: %MIN% 円
echo ─────────────────────────
endlocal
グループ別集計(部署ごとの人数と合計給与)
@echo off
setlocal enabledelayedexpansion

set "CSV=employees.csv"

:: 部署ごとのカウンタと合計を初期化
set /a CNT_SALES=0
set /a CNT_DEV=0
set /a CNT_OTHER=0
set /a SUM_SALES=0
set /a SUM_DEV=0
set /a SUM_OTHER=0

for /f "usebackq skip=1 tokens=1,2,3,4 delims=," %%A in ("%CSV%") do (
    set /a SALARY=%%D

    if /i "%%C"=="営業部" (
        set /a CNT_SALES+=1
        set /a SUM_SALES+=SALARY
    ) else if /i "%%C"=="開発部" (
        set /a CNT_DEV+=1
        set /a SUM_DEV+=SALARY
    ) else (
        set /a CNT_OTHER+=1
        set /a SUM_OTHER+=SALARY
    )
)

echo 部署別集計:
echo   営業部: %CNT_SALES%名 / 合計 %SUM_SALES%円
echo   開発部: %CNT_DEV%名 / 合計 %SUM_DEV%円
echo   その他: %CNT_OTHER%名 / 合計 %SUM_OTHER%円
endlocal

パターン7:CSVのメールアドレスに一括メール送信する

CSVのメールアドレス列を読み取り、PowerShell Send-MailMessage で個別送信する方法です。通知メールの一括配信・定期レポートの自動送付などに使えます。バッチファイルでエラー通知メールを自動送信する完全ガイドでエラー通知メールの詳細パターンを解説しています。

CSVのメールアドレスに個別送信
@echo off
setlocal enabledelayedexpansion

set "CSV=mail_list.csv"
set "SMTP=smtp.example.com"
set "FROM=batch@example.com"
set "SUBJECT=月次レポート送付のご案内"
set "BODY_FILE=mail_body.txt"
set /a OK=0
set /a FAIL=0

for /f "usebackq skip=1 tokens=1,2 delims=," %%A in ("%CSV%") do (
    set "TO_NAME=%%A"
    set "TO_MAIL=%%B"

    echo [送信中] !TO_NAME! (!TO_MAIL!)...

    powershell -NoProfile -Command ^
        "Send-MailMessage ^
         -From '%FROM%' ^
         -To '!TO_MAIL!' ^
         -Subject '%SUBJECT%' ^
         -Body ("!TO_NAME! 様
" + (Get-Content '%BODY_FILE%' -Raw)) ^
         -SmtpServer '%SMTP%' ^
         -Encoding UTF8" 2>nul

    if !ERRORLEVEL! EQU 0 (
        set /a OK+=1
        echo [OK]   !TO_MAIL!
    ) else (
        set /a FAIL+=1
        echo [FAIL] !TO_MAIL!
    )
)

echo.
echo 送信完了: 成功 %OK% / 失敗 %FAIL%
endlocal
メール送信前にドライランで確認する
実際に送信する前に、%%B(メールアドレス)を echo するだけのドライランで送信対象の一覧を確認してください。CSVの1行目がヘッダーなのに skip=1 を忘れると、ヘッダー行を宛先として送信しようとします。また、一度に大量送信するとSMTPサーバーのレート制限に引っかかる場合があるため、timeout /t 2 /nobreak で間隔を空けることを推奨します。

文字コード問題の対策

日本語を含むCSVをバッチで扱う場合、文字コードに起因する文字化けが発生しやすいです。バッチファイルで日本語が文字化けする完全原因解説と解決策ガイドで詳しく解説していますが、CSV処理での主なポイントを整理します。

CSVの文字コード for /f の動作 対処法
Shift-JIS(CP932) デフォルトで正常動作 特別な設定不要
UTF-8(BOMなし) 日本語が文字化けすることがある chcp 65001 をスクリプト冒頭に追加
UTF-8(BOM付き) 先頭にBOM文字(???)が混入 PowerShell Import-Csv -Encoding UTF8 を使う
UTF-16 LE for /f では読めない Get-Content -Encoding Unicode で読む
UTF-8 CSVを文字化けなく読む方法
@echo off

:: UTF-8 を有効化(日本語が文字化けする場合)
chcp 65001 > nul

setlocal enabledelayedexpansion

for /f "usebackq skip=1 tokens=1,2,3 delims=," %%A in ("utf8_data.csv") do (
    echo %%A / %%B / %%C
)

endlocal
PowerShellで文字コードに依存せず読む(推奨)
@echo off
setlocal enabledelayedexpansion

:: PowerShell経由で読み込み(-Encoding を指定して確実に文字コードを制御)
:: UTF-8の場合: -Encoding UTF8
:: Shift-JISの場合: -Encoding Default
powershell -NoProfile -Command ^
    "Import-Csv 'data.csv' -Encoding Default | ForEach-Object { ^
        $name = $_.氏名; ^
        $mail = $_.メールアドレス; ^
        "$name`t$mail" ^
    }" | (
    for /f "tokens=1,2 delims=	" %%A in ('more') do (
        echo 名前: %%A / メール: %%B
    )
)

endlocal

エラー処理とログ記録の実装

業務で使うCSV処理バッチは、途中のエラーをログに記録し、成功・失敗の件数を集計して報告する仕組みが必要です。バッチファイルでログを出力する方法完全ガイドのパターンと組み合わせた完成形を示します。

エラー記録・ログ付き完成版テンプレート
@echo off
setlocal enabledelayedexpansion

set "BASE=%~dp0"
set "CSV=%BASE%input.csv"
set "LOGDIR=%BASE%logs"
set "LOG=%LOGDIR%\process_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log"
set "ERR_CSV=%LOGDIR%\errors_%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.csv"

if not exist "%LOGDIR%" mkdir "%LOGDIR%"
if not exist "%CSV%" (
    echo [FATAL] 入力CSVが見つかりません: %CSV%
    exit /b 1
)

:: エラーCSVのヘッダー
echo 行番号,エラー内容,元データ > "%ERR_CSV%"

set /a LINE=0
set /a OK=0
set /a FAIL=0

echo ==================== >> "%LOG%"
echo 開始: %DATE% %TIME% >> "%LOG%"
echo ==================== >> "%LOG%"

for /f "usebackq skip=1 tokens=1,2,3,4 delims=," %%A in ("%CSV%") do (
    set /a LINE+=1

    set "NAME=%%A"
    set "MAIL=%%B"
    set "DEPT=%%C"
    set /a SALARY=%%D

    :: バリデーション: 名前が空
    if "!NAME!"=="" (
        echo !LINE!,名前が空,%%A%%B%%C%%D >> "%ERR_CSV%"
        echo [SKIP] 行!LINE!: 名前が空 >> "%LOG%"
        set /a FAIL+=1
    ) else if !SALARY! LEQ 0 (
        :: バリデーション: 給与が0以下
        echo !LINE!,給与が不正,%%A%%B%%C%%D >> "%ERR_CSV%"
        echo [SKIP] 行!LINE!: 給与が不正=!SALARY! >> "%LOG%"
        set /a FAIL+=1
    ) else (
        :: ── メイン処理 ──
        echo !LINE!: !NAME! (!DEPT!) !SALARY!円を処理中...
        echo [OK] 行!LINE!: !NAME! >> "%LOG%"
        set /a OK+=1
    )
)

echo ==================== >> "%LOG%"
echo 完了: OK=%OK% FAIL=%FAIL% >> "%LOG%"
echo ==================== >> "%LOG%"

echo.
echo 処理結果: 成功 %OK% 件 / エラー %FAIL% 件
if !FAIL! GTR 0 echo エラー明細: %ERR_CSV%
if !FAIL! GTR 0 exit /b 1
endlocal
exit /b 0

よくある質問(FAQ)

Qfor /f で読み込むと先頭行がスキップされてしまいます
Askip=1 オプションが付いていると1行目がスキップされます。ヘッダーなしのCSVには skip=1 を削除してください。逆にヘッダーが2行ある場合は skip=2 にします。
QCSVの列数が行によって変わる場合はどう処理しますか?
Atokens=1* を使うと、1列目を %%A に、残り全部を %%B に格納できます。%%B の中身をさらに解析する場合は for /f "tokens=1,2 delims=, " %%C in ("%%B") のように入れ子の for /f を使って処理します。
QCSVに空行が混在している場合、エラーになりませんか?
Afor /f は空行を自動的にスキップするため、空行が混在していても通常は問題ありません。ただし、空行に見えてもスペースやタブが入っている場合はスキップされず、カンマ分割で空の値が得られます。その場合は変数が空かどうかを if "!VAR!"=="" で確認してください。
Q出力CSVのエンコードをUTF-8にするにはどうすればいいですか?
Aバッチの echo ... >> output.csv はシステムデフォルト(Shift-JIS/CP932)で書き込みます。UTF-8で書き出すにはPowerShellを使います: powershell -Command "'内容' | Out-File -Encoding UTF8 output.csv -Append"。または処理全体をPowerShellスクリプトに移行し、最後に $results | Export-Csv -Encoding UTF8 で出力するのが最も確実です。
Q100万行のCSVを処理すると非常に時間がかかります。高速化できますか?
Aバッチの for /f は数万行程度なら実用的ですが、数十万行以上では時間がかかります。高速化の選択肢として①重い処理だけPowerShell・Pythonに切り出す、②MySQLの LOAD DATA INFILE やSQL Serverの BULK INSERT を使う(DBへの一括取込の場合)、③CSVをあらかじめ小さなチャンクに分割してから並列処理する、の3つが有効です。

まとめ

やりたいこと 使うパターン 主なポイント
CSV→ファイル一括コピー for /f + copy/move コピー元の存在確認・コピー先フォルダ作成を忘れずに
CSV→ファイルリネーム for /f + move renはフルパス移動不可のためmoveで代用
特定列の値で振り分け for /f + if /i if /i で大文字小文字無視の比較
数値範囲フィルタ set /a + if GEQ/LEQ set /a で文字列を数値に変換してから比較
列変換・計算列追加 set /a + 変数マッピング コードマッピングは set “DEPT_01=営業部” パターン
DBに一括登録 SQL文生成 + sqlcmd/mysql シングルクォートのエスケープを忘れずに
ダブルクォートCSV PowerShell Import-Csv カンマ含む値・クォートエスケープを正しく処理
集計(合計・最大など) for /f + set /a の累積 ループ内で変数を加算・比較して集計
メール一括送信 for /f + Send-MailMessage ドライランで対象確認してから本番送信
関連記事
バッチファイルでCSVファイルを読み込む方法完全ガイド:for /f でCSVを読み込む基本構文の詳細。
バッチファイルで複数のCSVファイルを1つにまとめる方法完全ガイド:複数のCSVファイルを1つにまとめる方法。
バッチファイルでデータベースにSQLを実行する方法完全ガイド:sqlcmd・mysql・psqlでDBにSQLを実行する方法。
テキストファイルをソートし重複行を削除する方法完全ガイド:CSVのソートと重複行削除のパターン。
複数バッチファイルを一括管理・実行するマスタースクリプト完全ガイド:CSVから読み込んだパラメータでバッチを一括実行するマスタースクリプト。