【bat】バッチファイルでCSVファイルを読み込む方法完全ガイド|for /f・ヘッダースキップ・列指定・文字コード・PowerShell・実践パターンまで

バッチファイルで CSV を読み込む方法は「for /f コマンド一択」です。ただし for /f には tokensdelimsskipusebackq などのオプションが多く、使い方を間違えると「ヘッダーが混入する」「列がずれる」「文字化けする」といった問題が起きます。

この記事では CSV 読み込みの全パターン を体系的に解説し、実務でそのまま使える実践例を3本紹介します。

この記事でわかること

  • for /f でCSVを読み込む基本構文(tokens・delims)
  • ヘッダー行をスキップする方法(skip=N)
  • 特定の列だけ取得する方法
  • クォート(”)で囲まれたフィールドへの対処
  • 文字コード(Shift-JIS・UTF-8)問題の解決策
  • 末尾スペース・空行・空フィールドの対処
  • 条件フィルタリング(特定列の値でスキップ)
  • PowerShell との連携で複雑なCSVを処理する方法
  • 落とし穴5選・実践例3本・FAQ6問
スポンサーリンク
  1. 1. CSV読み込みの基本構文
    1. 1-1. 最もシンプルな読み込み
    2. 1-2. すべての列を一括取得(tokens=*)
  2. 2. ヘッダー行をスキップする(skip=N)
  3. 3. 特定の列だけ取得する(tokens の指定方法)
  4. 4. クォート(”)で囲まれたフィールドへの対処
    1. 4-1. クォート付きフィールドの基本対処
    2. 4-2. フィールド内カンマへの現実的な対処(PowerShell委譲)
  5. 5. 文字コード問題(Shift-JIS・UTF-8)
    1. 5-1. UTF-8 CSVをShift-JISに変換してから読み込む
    2. 5-2. PowerShellで直接 UTF-8 CSVを処理する(推奨)
    3. 5-3. chcp でコードページを変更してから読み込む
  6. 6. 空行・空フィールド・末尾スペースへの対処
    1. 6-1. 空フィールドへの対処
    2. 6-2. 末尾スペースの除去
    3. 6-3. コメント行(# 先頭)のスキップ
  7. 7. 条件フィルタリング(特定の行だけ処理する)
  8. 8. 行番号・行数カウント
  9. 9. 疑似配列としてCSVデータを格納する
  10. 10. PowerShellとの連携で複雑なCSVを処理する
    1. 10-1. PowerShell で CSV を解析してバッチに渡す
    2. 10-2. PowerShellでCSVを加工して別のCSVに出力する
  11. 11. 落とし穴5選と対策
    1. 落とし穴1:for /f はデフォルトで ; 始まりの行をスキップする
    2. 落とし穴2:for /f は空行を自動スキップするため行番号がずれる
    3. 落とし穴3:ループ内で更新した変数を %変数% で読むと古い値になる
    4. 落とし穴4:パスにスペースがあるとファイルが開けない
    5. 落とし穴5:tokens の列番号と変数のアルファベットが直感と異なる
  12. 12. 実践例3本
    1. 実践例1:社員リストCSVを読み込んで部署ごとに集計する
    2. 実践例2:設定CSVから処理対象を読み込んでファイル一括コピー
    3. 実践例3:CSV の内容を検証してエラーレポートを出力する
  13. 13. まとめ:for /f オプション早見表
  14. FAQ

1. CSV読み込みの基本構文

バッチファイルで CSV を読み込むには for /f コマンドを使います。delims=, でカンマを区切り文字に指定し、tokens= で取得する列番号を指定します。詳細は FOR文の使い方完全ガイド外部コマンドの結果を変数に格納する方法 も参照してください。

1-1. 最もシンプルな読み込み

@echo off
setlocal enabledelayedexpansion

:: sample.csv の内容
:: Alice,30,Tokyo
:: Bob,25,Osaka
:: Charlie,35,Nagoya

for /f "tokens=1,2,3 delims=," %%A in (sample.csv) do (
    echo 名前: %%A  年齢: %%B  都市: %%C
)

tokens=1,2,3 で1〜3列目を取得します。取得した列は %%A(1列目)、%%B(2列目)、%%C(3列目)に格納されます。変数は %%A の次が %%B%%C … とアルファベット順に自動で割り当てられます。

1-2. すべての列を一括取得(tokens=*)

@echo off
:: tokens=* で行全体を1変数に格納(区切り文字の分割なし)
for /f "tokens=*" %%L in (sample.csv) do (
    echo %%L
)

:: usebackq でファイルパスにスペースが含まれる場合に対応
for /f "usebackq tokens=1,2,3 delims=," %%A in ("C:\work\data file.csv") do (
    echo %%A, %%B, %%C
)

usebackq を付けると、ファイルパスをバッククォート(`)ではなくダブルクォート(")で囲めます。パスにスペースが含まれる場合は必須です。

2. ヘッダー行をスキップする(skip=N)

CSV の1行目がヘッダーの場合、skip=1 でスキップできます。複数行のヘッダーがある場合は skip=N で行数を指定します。

@echo off
setlocal enabledelayedexpansion

:: sample.csv の内容
:: name,age,city       ← ヘッダー行(スキップ)
:: Alice,30,Tokyo
:: Bob,25,Osaka

:: skip=1 でヘッダー(1行目)をスキップ
for /f "skip=1 tokens=1,2,3 delims=," %%A in (sample.csv) do (
    echo 名前=%%A, 年齢=%%B, 都市=%%C
)

:: skip=2 で先頭2行をスキップ(コメント行などがある場合)
for /f "skip=2 tokens=1,2,3 delims=," %%A in (data.csv) do (
    echo %%A
)

3. 特定の列だけ取得する(tokens の指定方法)

列番号の指定方法を覚えると、必要な列だけを効率よく取得できます。

@echo off

:: 1列目と3列目だけ取得(2列目はスキップ)
for /f "skip=1 tokens=1,3 delims=," %%A in (sample.csv) do (
    echo 名前: %%A  都市: %%B
    :: ※ tokens=1,3 のとき 3列目は %%B(次のアルファベット)に格納される
)

:: 1〜5列目を取得
for /f "skip=1 tokens=1-5 delims=," %%A in (wide.csv) do (
    echo %%A, %%B, %%C, %%D, %%E
)

:: 3列目以降すべてを1変数にまとめて取得(tokens=3*)
for /f "skip=1 tokens=1,2,3* delims=," %%A in (sample.csv) do (
    echo 名前=%%A  年齢=%%B  残り=%%D
    :: tokens=3* のとき * 部分は %%D に格納される(%%Cは3列目)
)

tokens=N* を使うと、N列目以降の残り全体を1変数にまとめて取得できます。列数が可変の CSV に便利です。

4. クォート(”)で囲まれたフィールドへの対処

フィールドにカンマや改行を含む CSV では、フィールドをダブルクォートで囲む形式(RFC 4180)が使われます。for /f はこのクォートを自動的に解釈しないため、工夫が必要です。

4-1. クォート付きフィールドの基本対処

:: CSV の内容
:: "Alice","30","New York, USA"
:: "Bob","25","Los Angeles"

@echo off
:: delims=", で区切り → 先頭・末尾のクォートも区切り文字として扱う
for /f "skip=1 tokens=1,2,3 delims=","" %%A in (quoted.csv) do (
    echo 名前=%%A  年齢=%%B  都市=%%C
)

:: ただし「フィールド内のカンマ」は正しく扱えない(for /f の限界)
:: "New York, USA" は「New York」と「 USA」に分割されてしまう

4-2. フィールド内カンマへの現実的な対処(PowerShell委譲)

フィールド内にカンマが含まれる場合、for /f での正確な解析は困難です。PowerShellをバッチから呼び出す方法と組み合わせて処理するのが確実です。

@echo off
:: PowerShell に CSV 解析を委譲して1行ずつ処理
for /f "usebackq delims=" %%L in (`powershell -NoProfile -Command ^^
    "Import-Csv C:\work\data.csv | ForEach-Object { $_.name + "`t" + $_.age + "`t" + $_.city }"`) do (
    for /f "tokens=1,2,3 delims=	" %%A in ("%%L") do (
        echo 名前=%%A  年齢=%%B  都市=%%C
    )
)

PowerShell の Import-Csv はヘッダーを自動認識し、クォート内のカンマも正しく処理します。バッチとのデータ受け渡しにはタブ文字(`t)など、CSVに含まれない文字を区切りとして使うと確実です。

5. 文字コード問題(Shift-JIS・UTF-8)

for /f はデフォルトでシステムのコードページ(日本語 Windows では Shift-JIS = CP932)でファイルを読み込みます。UTF-8 で保存された CSV を読み込むと日本語が文字化けします。バッチでUnicodeファイルを扱えない原因と解決策 も参照してください。

5-1. UTF-8 CSVをShift-JISに変換してから読み込む

@echo off
:: UTF-8 CSV を一時ファイルに Shift-JIS 変換して読み込む
set "INPUT=C:\work\utf8_data.csv"
set "WORK=%TEMP%\converted_%RANDOM%.csv"

powershell -NoProfile -Command ^^
    "Get-Content -Path "%INPUT%" -Encoding UTF8 | Out-File -FilePath "%WORK%" -Encoding Default"

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

del "%WORK%"

5-2. PowerShellで直接 UTF-8 CSVを処理する(推奨)

@echo off
:: UTF-8 CSV は PowerShell で読んで Shift-JIS に変換しながら for /f に渡す
for /f "usebackq tokens=1,2,3 delims=," %%A in (`powershell -NoProfile -Command ^^
    "Get-Content -Path C:\work\data.csv -Encoding UTF8"`) do (
    echo %%A, %%B, %%C
)

5-3. chcp でコードページを変更してから読み込む

@echo off
:: UTF-8 モードで実行(ただし日本語の表示は端末の設定にも依存)
chcp 65001 >nul

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

6. 空行・空フィールド・末尾スペースへの対処

for /f は空行を自動的にスキップします。空フィールドや末尾スペースには別途対処が必要です。変数の末尾に余計な空白が入ってしまうときの対策 も参照してください。

6-1. 空フィールドへの対処

:: CSV の内容
:: Alice,,Tokyo    ← 2列目が空
:: Bob,25,

@echo off
setlocal enabledelayedexpansion

for /f "skip=1 tokens=1,2,3 delims=," %%A in (sample.csv) do (
    set "NAME=%%A"
    set "AGE=%%B"
    set "CITY=%%C"

    :: 空フィールドはデフォルト値を設定
    if "!AGE!"=="" set "AGE=不明"
    if "!CITY!"=="" set "CITY=不明"

    echo 名前=!NAME!  年齢=!AGE!  都市=!CITY!
)

6-2. 末尾スペースの除去

@echo off
setlocal enabledelayedexpansion

for /f "usebackq skip=1 tokens=1,2 delims=," %%A in ("data.csv") do (
    set "VAL=%%A"
    :: 末尾のスペースを除去(変数の末尾スペース対策)
    for /l %%i in (1,1,5) do if "!VAL:~-1!"==" " set "VAL=!VAL:~0,-1!"
    echo [!VAL!]
)

6-3. コメント行(# 先頭)のスキップ

@echo off
setlocal enabledelayedexpansion

:: for /f の eol= オプションでコメント文字を指定(デフォルトは ;)
:: # 先頭の行をコメントとしてスキップ
for /f "usebackq eol=# skip=1 tokens=1,2,3 delims=," %%A in ("data.csv") do (
    echo %%A, %%B, %%C
)

:: ; 先頭(デフォルトの eol)の行も自動スキップされるため注意
:: セミコロン始まりのデータが欠落する場合は eol= で変更する
for /f "usebackq eol=| tokens=1,2,3 delims=," %%A in ("data.csv") do (
    echo %%A
)

7. 条件フィルタリング(特定の行だけ処理する)

特定の列の値で行を絞り込みながら処理するパターンです。バッチファイルで条件分岐する方法完全ガイド も参照してください。

@echo off
setlocal enabledelayedexpansion

:: CSV の内容
:: name,status,score
:: Alice,active,85
:: Bob,inactive,72
:: Charlie,active,91

set "COUNT=0"
set "TOTAL=0"

for /f "skip=1 tokens=1,2,3 delims=," %%A in (data.csv) do (
    :: status が active の行のみ処理
    if /i "%%B"=="active" (
        set /a COUNT+=1
        set /a TOTAL+=%%C
        echo [対象] %%A: スコア=%%C
    )
)

if !COUNT! GTR 0 (
    set /a AVG=TOTAL/COUNT
    echo 平均スコア: !AVG! (!COUNT! 件)
) else (
    echo 対象データがありません
)

8. 行番号・行数カウント

CSV の行数を数えたり、何行目かを記録しながら処理するパターンです。

@echo off
setlocal enabledelayedexpansion

set "LINE=0"

for /f "skip=1 tokens=1,2,3 delims=," %%A in (data.csv) do (
    set /a LINE+=1
    echo !LINE!行目: %%A, %%B, %%C
)

echo 合計: !LINE! 行

:: CSV のヘッダーを含む総行数を取得
set "TOTAL_LINES=0"
for /f %%C in ('find /c /v "" ^< data.csv') do set "TOTAL_LINES=%%C"
echo ファイル総行数(ヘッダー含む): %TOTAL_LINES%

9. 疑似配列としてCSVデータを格納する

CSV の各行を連番付き変数(疑似配列)に格納して後で参照するパターンです。バッチファイルで配列を使う方法完全ガイド も参照してください。

@echo off
setlocal enabledelayedexpansion

set "IDX=0"

:: CSV データを疑似配列に格納
for /f "skip=1 tokens=1,2,3 delims=," %%A in (data.csv) do (
    set /a IDX+=1
    set "NAME[!IDX!]=%%A"
    set "AGE[!IDX!]=%%B"
    set "CITY[!IDX!]=%%C"
)
set "MAX=%IDX%"

:: 疑似配列を逆順に表示(後から参照する例)
for /l %%i in (%MAX%,-1,1) do (
    echo !NAME[%%i]! / !AGE[%%i]! / !CITY[%%i]!
)

10. PowerShellとの連携で複雑なCSVを処理する

クォート内カンマ・UTF-8・列数可変など for /f では対応しにくいケースは PowerShell に委譲するのが最善です。PowerShellをバッチファイルから呼び出す方法 も参照してください。

10-1. PowerShell で CSV を解析してバッチに渡す

@echo off
setlocal enabledelayedexpansion

:: Import-Csv でヘッダー・クォート・UTF-8 を自動処理
:: タブ区切りでバッチに出力し、for /f で受け取る
for /f "usebackq tokens=1,2,3 delims=	" %%A in (`
    powershell -NoProfile -Command 
    "Import-Csv C:\work\data.csv -Encoding UTF8 | ForEach-Object { $_.name+[char]9+$_.age+[char]9+$_.city }"
`) do (
    echo 名前=%%A  年齢=%%B  都市=%%C
)

10-2. PowerShellでCSVを加工して別のCSVに出力する

@echo off
:: CSV をフィルタリング・加工して別ファイルに出力する場合は純粋にPowerShellで完結させる
powershell -NoProfile -Command "
    Import-Csv C:\work\input.csv -Encoding UTF8
    | Where-Object { $_.status -eq "active" }
    | Select-Object name, score
    | Export-Csv C:\work\output.csv -Encoding UTF8 -NoTypeInformation
"

:: 出力したCSVをバッチで続けて処理
for /f "skip=1 tokens=1,2 delims=," %%A in (C:\work\output.csv) do (
    echo %%A の スコア: %%B
)

11. 落とし穴5選と対策

落とし穴1:for /f はデフォルトで ; 始まりの行をスキップする

:: CSV のフィールドが ; で始まる場合、その行が自動スキップされる
:: 例: ;comment,data → for /f がコメント行と見なしてスキップ

:: 対策: eol= に ; 以外の文字を指定する(ほぼ使わない文字を指定)
for /f "usebackq eol=| tokens=1,2 delims=," %%A in ("data.csv") do (
    echo %%A
)

落とし穴2:for /f は空行を自動スキップするため行番号がずれる

:: for /f は空行(改行だけの行)を読み飛ばす
:: → 行番号でデータを管理している場合にズレが発生する

:: 対策: PowerShell の Get-Content など空行を保持する方法を使う
for /f "usebackq delims=" %%L in (`powershell -NoProfile -Command "Get-Content C:\work\data.csv"`) do (
    :: 空行は空文字として受け取れる
    echo [%%L]
)

落とし穴3:ループ内で更新した変数を %変数% で読むと古い値になる

:: NG: for /f ループ内で set した変数を %COUNT% で読むと 0 のまま
set "COUNT=0"
for /f "tokens=1 delims=," %%A in (data.csv) do (
    set /a COUNT+=1
    echo %COUNT% 行目: %%A   :: ← 常に0が表示される
)

:: OK: setlocal enabledelayedexpansion + !COUNT! を使う
setlocal enabledelayedexpansion
set "COUNT=0"
for /f "tokens=1 delims=," %%A in (data.csv) do (
    set /a COUNT+=1
    echo !COUNT! 行目: %%A
)

setlocal enabledelayedexpansion 完全ガイド変数展開が動かない原因と修正方法 も参照してください。

落とし穴4:パスにスペースがあるとファイルが開けない

:: NG: パスにスペースを含む場合、引用符だけでは不十分
for /f "tokens=1 delims=," %%A in ("C:\my folder\data.csv") do (
    echo %%A   :: ← ファイルが開けない(for /f の引数が文字列として解釈される)
)

:: OK: usebackq を追加してダブルクォートをファイル名として解釈させる
for /f "usebackq tokens=1 delims=," %%A in ("C:\my folder\data.csv") do (
    echo %%A
)

落とし穴5:tokens の列番号と変数のアルファベットが直感と異なる

:: tokens=1,3 のとき
:: %%A → 1列目、%%B → 3列目(2列目はスキップされるが %%B は次のアルファベット)
for /f "tokens=1,3 delims=," %%A in (data.csv) do (
    echo 1列目=%%A  3列目=%%B
)

:: tokens=2,4,6 のとき
:: %%A → 2列目、%%B → 4列目、%%C → 6列目
for /f "tokens=2,4,6 delims=," %%A in (data.csv) do (
    echo %%A, %%B, %%C
)

取得する最初の列が必ず %%A に格納され、以降はアルファベット順に割り当てられます。スキップした列番号は飛ばされますが、変数名は連続したアルファベットになります。

12. 実践例3本

実践例1:社員リストCSVを読み込んで部署ごとに集計する

@echo off
setlocal enabledelayedexpansion

:: employees.csv の内容
:: name,dept,salary
:: Alice,Engineering,500000
:: Bob,Sales,400000
:: Charlie,Engineering,520000

set "ENG_COUNT=0" & set "ENG_TOTAL=0"
set "SALES_COUNT=0" & set "SALES_TOTAL=0"

for /f "usebackq skip=1 tokens=1,2,3 delims=," %%A in ("employees.csv") do (
    if /i "%%B"=="Engineering" (
        set /a ENG_COUNT+=1
        set /a ENG_TOTAL+=%%C
    ) else if /i "%%B"=="Sales" (
        set /a SALES_COUNT+=1
        set /a SALES_TOTAL+=%%C
    )
)

echo ===== 部署別集計 =====
if !ENG_COUNT! GTR 0 (
    set /a ENG_AVG=ENG_TOTAL/ENG_COUNT
    echo Engineering: !ENG_COUNT!人  合計=!ENG_TOTAL!  平均=!ENG_AVG!
)
if !SALES_COUNT! GTR 0 (
    set /a SALES_AVG=SALES_TOTAL/SALES_COUNT
    echo Sales:       !SALES_COUNT!人  合計=!SALES_TOTAL!  平均=!SALES_AVG!
)

実践例2:設定CSVから処理対象を読み込んでファイル一括コピー

コピー元・コピー先のパスリストを CSV で管理して一括コピーするスクリプトです。

@echo off
setlocal enabledelayedexpansion

:: copy_list.csv の内容
:: src,dst
:: C:\work\report.xlsx,D:\backup\report.xlsx
:: C:\work\data.csv,D:\backup\data.csv

set "OK=0"
set "FAIL=0"

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

    :: コピー元の存在確認
    if not exist "!SRC!" (
        echo [SKIP] 見つかりません: !SRC!
        set /a FAIL+=1
    ) else (
        copy /y "!SRC!" "!DST!" >nul 2>&1
        if !ERRORLEVEL! EQU 0 (
            echo [OK] !SRC! → !DST!
            set /a OK+=1
        ) else (
            echo [ERROR] コピー失敗: !SRC!
            set /a FAIL+=1
        )
    )
)

echo ===== 結果: 成功 !OK! 件 / 失敗・スキップ !FAIL! 件 =====
if !FAIL! GTR 0 exit /b 1

実践例3:CSV の内容を検証してエラーレポートを出力する

CSV の各行をバリデーション(必須列・数値チェック)してエラー行をレポートするスクリプトです。

@echo off
setlocal enabledelayedexpansion

set "INPUT=C:\work\import.csv"
set "REPORT=C:\work\error_report.txt"
set "LINE=0"
set "ERR_COUNT=0"

echo バリデーション開始: %date% %time% > "%REPORT%"

for /f "usebackq skip=1 tokens=1,2,3 delims=," %%A in ("%INPUT%") do (
    set /a LINE+=1
    set "NAME=%%A"
    set "AGE=%%B"
    set "CITY=%%C"
    set "ROW_ERR="

    :: 必須チェック
    if "!NAME!"=="" (
        echo !LINE!行目: name が空です >> "%REPORT%"
        set "ROW_ERR=1"
    )
    :: 数値チェック(AGE が0以上の整数か)
    :: echo で出力して findstr の正規表現 ^[0-9][0-9]*$ でチェック
    set "IS_NUM="
    echo !AGE! | findstr /r "^[0-9][0-9]*$" >nul 2>&1
    if not errorlevel 1 set "IS_NUM=1"
    if not defined IS_NUM (
        echo !LINE!行目: age が正の整数ではありません(%%B) >> "%REPORT%"
        set "ROW_ERR=1"
    )
    :: 範囲チェック(数値の場合のみ)
    if defined IS_NUM (
        if !AGE! GTR 150 ( echo !LINE!行目: age が範囲外です(%%B) >> "%REPORT%" & set "ROW_ERR=1" )
    )

    if defined ROW_ERR set /a ERR_COUNT+=1
)

echo 検証完了: !LINE!行中 !ERR_COUNT!行にエラー >> "%REPORT%"
echo 結果: !LINE!行中 !ERR_COUNT!行にエラー(詳細: %REPORT%)
if !ERR_COUNT! GTR 0 exit /b 1

13. まとめ:for /f オプション早見表

オプション 意味 使用例
delims=, 区切り文字をカンマに設定 CSV の列を分割
tokens=1,2,3 取得する列番号を指定 1〜3列目を %%A %%B %%C に格納
tokens=1-5 1〜5列目を連続取得 %%A〜%%E に格納
tokens=2* 2列目以降を残り全部として取得 %%A=2列目、%%B=3列目以降全体
skip=N 先頭N行をスキップ ヘッダー行のスキップ
usebackq ファイル名を ” で囲める パスにスペースが含まれる場合
eol=文字 行コメント文字を変更(デフォルト;) eol=| で ; 始まりを読む
tokens=* 行全体を1変数に格納 分割せず1行まるごと取得

CSV 関連の記事として CSVファイルを分割する方法完全ガイドバッチファイルでCSVを結合する方法完全ガイド複数のCSVファイルを一つにまとめる方法 もあわせて参照してください。

FAQ

Q. for /f でヘッダー行だけ取得するにはどうしますか?
A. skip=1 ではなく、逆にスキップせずに最初の行だけ処理する方法は少し工夫が必要です。フラグ変数で「初回だけ処理する」制御を使います:set "FIRST=1" → ループ内で if defined FIRST (処理) & set "FIRST=" のように実装します。
Q. UTF-8 のCSVを読み込むと文字化けします。
A. for /f は Shift-JIS(CP932)でファイルを読み込むため、UTF-8 CSVは文字化けします。PowerShell の Get-Content -Encoding UTF8 で変換してから読み込むか、chcp 65001 で UTF-8 モードに切り替えてください(→ 5節)。
Q. フィールド内にカンマが含まれるCSVは処理できますか?
A. for /f だけでは正しく処理できません。PowerShell の Import-Csv に委譲するのが最善策です(→ 4節)。Import-Csv はクォート内のカンマを正しく扱います。
Q. ループ内でカウンター変数が更新されません。
A. for /f ブロックの内側では %COUNT% の展開がブロック進入時に固定されます。setlocal enabledelayedexpansion を宣言して !COUNT! で参照してください(→ 落とし穴3)。
Q. for /f でスペースを含むパスのCSVが読み込めません。
A. usebackq オプションを追加してください。for /f "usebackq tokens=... delims=," %%A in ("パス\data.csv") のように、ファイルパスをダブルクォートで囲んで usebackq で指定します(→ 落とし穴4)。
Q. 列数が行によって異なるCSVを処理できますか?
A. tokens=1,2,3* を使うと3列目以降を1変数にまとめて取得できます。列数が完全に可変な場合は PowerShell の Import-Csv または ConvertFrom-Csv を使う方が確実です。