CSVファイルが大きくなりすぎて処理が遅い、Excelが開けない、ツールの行数制限に引っかかる――そんな場面でCSVの分割は必須スキルです。バッチファイル(bat)だけで、サーバー上でもローカルでも追加ツール不要で実現できます。
この記事でできること
- 行数を指定して均等にCSVを分割する
- ヘッダー行を全分割ファイルに保持する
- 特定の列の値でグループ分割する(月別・地域別など)
- 連番ゼロ埋めのファイル名を付ける(output_001.csv形式)
- PowerShellで100万行超の大容量CSVを高速処理する
- 実践例3本(月次売上・ログ・注文CSV)と落とし穴・FAQも解説
方法の比較
用途に応じて最適な方法を選んでください。
| 方法 | ヘッダー保持 | 列値分割 | 向き不向き |
|---|---|---|---|
| for /f + カウンタ | △要設定 | × | シンプルな均等分割 |
| for /f + ヘッダーコピー | ○ | × | ヘッダー付き均等分割 |
| for /f + 列値判定 | ○ | ○ | 値でグループ分け |
| PowerShell | ○ | ○ | 大容量・高速処理 |
方法1: 行数指定で均等分割
for /f ループでカウンタを管理し、指定した行数に達したら新しいファイルに切り替えます。最もシンプルな均等分割の方法です。
@echo off
setlocal enabledelayedexpansion
set "INPUT_FILE=data.csv"
set "OUTPUT_FOLDER=output"
set "LINES_PER_FILE=1000"
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
set /a FILE_COUNT=1
set /a LINE_COUNT=0
for /f "usebackq tokens=* delims=" %%A in ("%INPUT_FILE%") do (
set /a LINE_COUNT+=1
if !LINE_COUNT! gtr !LINES_PER_FILE! (
set /a FILE_COUNT+=1
set /a LINE_COUNT=1
)
echo %%A>>"%OUTPUT_FOLDER%\output_!FILE_COUNT!.csv"
)
echo 完了: %FILE_COUNT% ファイルに分割しました
ファイル名を output_001.csv のようにゼロ埋めしたい場合は以下を使います。
@echo off
setlocal enabledelayedexpansion
set "INPUT_FILE=data.csv"
set "OUTPUT_FOLDER=output"
set "LINES_PER_FILE=1000"
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
set /a FILE_COUNT=1
set /a LINE_COUNT=0
for /f "usebackq tokens=* delims=" %%A in ("%INPUT_FILE%") do (
set /a LINE_COUNT+=1
if !LINE_COUNT! gtr !LINES_PER_FILE! (
set /a FILE_COUNT+=1
set /a LINE_COUNT=1
)
:: ゼロ埋め: 1000+番号 の下3桁を使う
set /a PADDED=1000+!FILE_COUNT!
set "PADNAME=!PADDED:~1!"
echo %%A>>"%OUTPUT_FOLDER%\output_!PADNAME!.csv"
)
echo 完了: %FILE_COUNT% ファイルに分割しました
方法2: ヘッダー行を全分割ファイルに保持
1行目のヘッダーを変数に保存し、新しいファイルを作成するたびに先頭に挿入します。分析ツールへの読み込みや、分割後の再結合時にも列名が残るため便利です。
@echo off
setlocal enabledelayedexpansion
set "INPUT_FILE=data.csv"
set "OUTPUT_FOLDER=output"
set "LINES_PER_FILE=1000"
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
set /a FILE_COUNT=1
set /a LINE_COUNT=0
set "HEADER="
set "HEADER_SAVED=0"
for /f "usebackq tokens=* delims=" %%A in ("%INPUT_FILE%") do (
if !HEADER_SAVED! equ 0 (
:: 1行目をヘッダーとして保存
set "HEADER=%%A"
set "HEADER_SAVED=1"
:: 最初のファイルにヘッダーを書き込む
echo !HEADER!>>"%OUTPUT_FOLDER%\output_!FILE_COUNT!.csv"
) else (
set /a LINE_COUNT+=1
if !LINE_COUNT! gtr !LINES_PER_FILE! (
set /a FILE_COUNT+=1
set /a LINE_COUNT=1
:: 新ファイルにも先頭ヘッダーを書き込む
echo !HEADER!>>"%OUTPUT_FOLDER%\output_!FILE_COUNT!.csv"
)
echo %%A>>"%OUTPUT_FOLDER%\output_!FILE_COUNT!.csv"
)
)
echo 完了: %FILE_COUNT% ファイルに分割しました(各ファイルにヘッダー付き)
方法3: 特定の列の値でグループ分割
CSVの特定列(ここでは3列目の地域コードなど)の値ごとに別ファイルへ書き込みます。月別・部署別・カテゴリ別などの集計前処理に活用できます。
@echo off
setlocal enabledelayedexpansion
set "INPUT=data.csv"
set "OUTPUT_FOLDER=output"
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
:: tokens=1,2,3,* で1〜3列目と残り全体を取得
for /f "usebackq tokens=1,2,3,* delims=," %%A in ("%INPUT%") do (
:: 3列目の値をキーにする
set "KEY=%%C"
echo %%A,%%B,%%C,%%D>>"%OUTPUT_FOLDER%\output_!KEY!.csv"
)
echo 完了: 列値ごとにファイルを生成しました
ヘッダー行を各ファイルに付けたい場合は、キーごとに初回書き込みフラグを管理します。
@echo off
setlocal enabledelayedexpansion
set "INPUT=data.csv"
set "OUTPUT_FOLDER=output"
set "HEADER_SAVED=0"
set "HEADER="
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
for /f "usebackq tokens=1,2,3,* delims=," %%A in ("%INPUT%") do (
if !HEADER_SAVED! equ 0 (
set "HEADER=%%A,%%B,%%C,%%D"
set "HEADER_SAVED=1"
) else (
set "KEY=%%C"
:: 各KEYに対して初回のみヘッダーを書き込む
if not defined FIRSTTIME_!KEY! (
set "FIRSTTIME_!KEY!=1"
echo !HEADER!>>"%OUTPUT_FOLDER%\output_!KEY!.csv"
)
echo %%A,%%B,%%C,%%D>>"%OUTPUT_FOLDER%\output_!KEY!.csv"
)
)
echo 完了
方法4: 日付列を使ってファイル名に日付を入れる
1列目が YYYY-MM-DD 形式の日付CSVを月別(YYYY-MM)に分割します。ログや売上データをまとめて月別に仕分けするときに便利です。
@echo off
setlocal enabledelayedexpansion
set "INPUT=sales.csv"
set "OUTPUT_FOLDER=monthly"
set "HEADER_SAVED=0"
set "HEADER="
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
for /f "usebackq tokens=1,* delims=," %%A in ("%INPUT%") do (
if !HEADER_SAVED! equ 0 (
set "HEADER=%%A,%%B"
set "HEADER_SAVED=1"
) else (
:: YYYY-MM-DD の先頭7文字(YYYY-MM)を取得
set "DATE_COL=%%A"
set "MONTH=!DATE_COL:~0,7!"
:: 月ファイルへの初回書き込み時にヘッダーを追加
if not defined FIRSTTIME_!MONTH! (
set "FIRSTTIME_!MONTH!=1"
echo !HEADER!>>"%OUTPUT_FOLDER%\output_!MONTH!.csv"
)
echo %%A,%%B>>"%OUTPUT_FOLDER%\output_!MONTH!.csv"
)
)
echo 完了: 月別ファイルを生成しました
方法5: PowerShellで高速・大容量対応
100万行を超えるCSVに for /f を使うと非常に遅くなります。PowerShellの StreamReader/StreamWriter を使えば大容量でも高速に処理できます。bat から powershell -NoProfile -Command で呼び出せます。
@echo off
setlocal
set "INPUT=C:\data\large.csv"
set "OUTPUT_FOLDER=C:\data\output"
set "LINES_PER_FILE=10000"
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
powershell -NoProfile -Command "
$input = '%INPUT%'
$outDir = '%OUTPUT_FOLDER%'
$maxLines = %LINES_PER_FILE%
$reader = [System.IO.StreamReader]::new($input, [System.Text.Encoding]::UTF8)
$header = $reader.ReadLine()
$count = 0
$fileNum = 1
$padded = $fileNum.ToString('000')
$writer = [System.IO.StreamWriter]::new(\"$outDir\output_$padded.csv\", $false, [System.Text.Encoding]::UTF8)
$writer.WriteLine($header)
while (-not $reader.EndOfStream) {
$line = $reader.ReadLine()
$count++
if ($count -gt $maxLines) {
$writer.Close()
$fileNum++
$padded = $fileNum.ToString('000')
$writer = [System.IO.StreamWriter]::new(\"$outDir\output_$padded.csv\", $false, [System.Text.Encoding]::UTF8)
$writer.WriteLine($header)
$count = 1
}
$writer.WriteLine($line)
}
$writer.Close()
$reader.Close()
Write-Host \"完了: $fileNum ファイル生成\"
"
PowerShellスクリプトを .ps1 ファイルに分離してbatから呼ぶ方法もシンプルです。
:: split.bat から split.ps1 を呼び出す例 @echo off powershell -NoProfile -ExecutionPolicy Bypass -File "split.ps1" -InputFile "data.csv" -OutputDir "output" -LinesPerFile 10000 echo PowerShell処理完了
実践例A: 月次売上CSVを月別ファイルに分割
想定データ: 1列目が日付(YYYY-MM-DD)、2列目が商品名、3列目が金額のCSV。ヘッダーありで月別に sales_YYYY-MM.csv を生成します。-dry オプションでドライラン(ファイル作成なし)も確認できます。
@echo off
setlocal enabledelayedexpansion
set "INPUT=sales_all.csv"
set "OUTPUT_FOLDER=monthly_sales"
set "DRY_RUN=0"
if "%1"=="-dry" set "DRY_RUN=1"
if %DRY_RUN%==1 echo [ドライラン] ファイルは作成しません
if not exist "%OUTPUT_FOLDER%" (
if %DRY_RUN%==0 mkdir "%OUTPUT_FOLDER%"
)
set "HEADER_SAVED=0"
set "HEADER="
for /f "usebackq tokens=1,2,3 delims=," %%A in ("%INPUT%") do (
if !HEADER_SAVED! equ 0 (
set "HEADER=%%A,%%B,%%C"
set "HEADER_SAVED=1"
) else (
set "DATE_COL=%%A"
set "MONTH=!DATE_COL:~0,7!"
if %DRY_RUN%==1 (
echo [DRY] %%A,%%B,%%C -> sales_!MONTH!.csv
) else (
if not defined FIRST_!MONTH! (
set "FIRST_!MONTH!=1"
echo !HEADER!>>"%OUTPUT_FOLDER%\sales_!MONTH!.csv"
)
echo %%A,%%B,%%C>>"%OUTPUT_FOLDER%\sales_!MONTH!.csv"
)
)
)
echo 処理完了
実践例B: 大容量ログCSVをヘッダー付きで1000行ずつ分割
ヘッダー保持+ゼロ埋めファイル名の完全実装です。処理完了時に生成ファイル数と総行数を表示します。
@echo off
setlocal enabledelayedexpansion
set "INPUT=system_log.csv"
set "OUTPUT_FOLDER=log_split"
set "LINES_PER_FILE=1000"
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
set /a FILE_COUNT=1
set /a LINE_COUNT=0
set /a TOTAL_LINES=0
set "HEADER_SAVED=0"
set "HEADER="
for /f "usebackq tokens=* delims=" %%A in ("%INPUT%") do (
if !HEADER_SAVED! equ 0 (
set "HEADER=%%A"
set "HEADER_SAVED=1"
set /a PADDED=1000+!FILE_COUNT!
set "PADNAME=!PADDED:~1!"
echo !HEADER!>>"%OUTPUT_FOLDER%\log_!PADNAME!.csv"
) else (
set /a LINE_COUNT+=1
set /a TOTAL_LINES+=1
if !LINE_COUNT! gtr !LINES_PER_FILE! (
set /a FILE_COUNT+=1
set /a LINE_COUNT=1
set /a PADDED=1000+!FILE_COUNT!
set "PADNAME=!PADDED:~1!"
echo !HEADER!>>"%OUTPUT_FOLDER%\log_!PADNAME!.csv"
)
set /a PADDED=1000+!FILE_COUNT!
set "PADNAME=!PADDED:~1!"
echo %%A>>"%OUTPUT_FOLDER%\log_!PADNAME!.csv"
)
)
echo ===========================
echo 処理完了
echo 総データ行数: %TOTAL_LINES% 行
echo 生成ファイル数: %FILE_COUNT% ファイル
echo 出力先: %OUTPUT_FOLDER%
echo ===========================
実践例C: 注文CSVを地域コード列で分割してZIPに圧縮
3列目の地域コード(north/south/east/west)でファイルを分割し、PowerShellの Compress-Archive で各ファイルをZIP化します。圧縮後に元の分割ファイルを削除するオプションも含みます。
@echo off
setlocal enabledelayedexpansion
set "INPUT=orders.csv"
set "OUTPUT_FOLDER=orders_by_region"
set "DELETE_CSV=1"
if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%"
set "HEADER_SAVED=0"
set "HEADER="
:: 地域コード列(3列目)でグループ分割
for /f "usebackq tokens=1,2,3,* delims=," %%A in ("%INPUT%") do (
if !HEADER_SAVED! equ 0 (
set "HEADER=%%A,%%B,%%C,%%D"
set "HEADER_SAVED=1"
) else (
set "REGION=%%C"
if not defined FIRST_!REGION! (
set "FIRST_!REGION!=1"
echo !HEADER!>>"%OUTPUT_FOLDER%\orders_!REGION!.csv"
)
echo %%A,%%B,%%C,%%D>>"%OUTPUT_FOLDER%\orders_!REGION!.csv"
)
)
echo 分割完了。ZIP圧縮を開始します...
:: PowerShellで各CSVをZIPに圧縮
for %%F in ("%OUTPUT_FOLDER%\*.csv") do (
set "CSV_PATH=%%~fF"
set "ZIP_PATH=%%~dpnF.zip"
powershell -NoProfile -Command "Compress-Archive -Path '!CSV_PATH!' -DestinationPath '!ZIP_PATH!' -Force"
echo 圧縮完了: %%~nxF -> %%~nF.zip
if %DELETE_CSV%==1 del "!CSV_PATH!"
)
echo 全処理完了
よくある落とし穴
1. for /f はCSV内のカンマを区切り文字として扱う
for /f "tokens=1,2 delims=," と書くと、CSVの各フィールドが分割されます。行全体をそのまま取得したい場合は tokens=* delims= を使って区切り文字を無効化します。
:: NG: 行が列ごとに分割される
for /f "usebackq tokens=1,2 delims=," %%A in ("data.csv") do echo %%A,%%B
:: OK: 行全体を%%Aに取得
for /f "usebackq tokens=* delims=" %%A in ("data.csv") do echo %%A
2. ダブルクォートを含むフィールドで行が分割される
CSVのフィールド内にカンマや改行を含む場合、ダブルクォートで囲む RFC 4180 形式になります。for /f はこの形式に対応していないため、複雑なCSVにはPowerShellの Import-Csv を使うのが安全です。
:: PowerShellでRFC4180形式CSVを正しく読む
powershell -NoProfile -Command "
$rows = Import-Csv -Path 'data.csv' -Encoding UTF8
foreach ($row in $rows) {
Write-Output $row.Date
}
"
3. 日本語・マルチバイト文字が文字化けする
batのデフォルトはShift_JISです。UTF-8のCSVを扱う場合は chcp 65001 でコードページを変更します。ただし、BOM付きUTF-8の場合はBOMがヘッダーの先頭に混入することがあるため注意が必要です。大容量・マルチバイトCSVにはPowerShellを推奨します。
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
:: UTF-8 CSVを処理(BOMなしが望ましい)
for /f "usebackq tokens=* delims=" %%A in ("utf8_data.csv") do (
echo %%A
)
4. 空行がfor /fにスキップされる
for /f は空行を自動的にスキップします。CSVに空行が含まれる場合、出力ファイルの行数が予想より少なくなります。空行もそのまま保持したい場合は findstr /n "^" を組み合わせてください。
:: 空行を保持しながら行番号付きで読む
for /f "tokens=1,* delims=:" %%A in ('findstr /n "^" data.csv') do (
:: %%B が空行の場合でも処理される
echo %%B
)
5. ファイルハンドル未解放で書き込み漏れ
batの echo %%A>> はループが終わるまで同じファイルに追記し続けます。ループ終了後に endlocal を呼ぶことで変数を確実にクリアできます。大量ファイルへの書き込みが連続する場合、一時的にファイルロックが残ることがあるためPowerShellの StreamWriter を使う方が確実です。
@echo off
setlocal enabledelayedexpansion
for /f "usebackq tokens=* delims=" %%A in ("data.csv") do (
echo %%A>>output.csv
)
:: endlocal で遅延展開変数を解放
endlocal
echo 書き込み完了
よくある質問(FAQ)
ヘッダーフラグのカウンタを2にして、2行目までを保存します。
set "HEADER_COUNT=0"
set "HEADER1="
set "HEADER2="
for /f "usebackq tokens=* delims=" %%A in ("data.csv") do (
if !HEADER_COUNT! lss 2 (
set /a HEADER_COUNT+=1
if !HEADER_COUNT! equ 1 set "HEADER1=%%A"
if !HEADER_COUNT! equ 2 set "HEADER2=%%A"
echo %%A>>output_!FILE_COUNT!.csv
) else (
:: 新ファイル作成時は2行ヘッダーを先頭に書く
echo %%A>>output_!FILE_COUNT!.csv
)
)
実際のファイル書き込みをスキップして行数だけをカウントします。-dry オプションを付けて実行してください。
@echo off
setlocal enabledelayedexpansion
set "INPUT=data.csv"
set "LINES_PER_FILE=1000"
set "DRY_RUN=0"
if "%1"=="-dry" set "DRY_RUN=1"
set /a FILE_COUNT=1
set /a LINE_COUNT=0
for /f "usebackq tokens=* delims=" %%A in ("%INPUT%") do (
set /a LINE_COUNT+=1
if !LINE_COUNT! gtr !LINES_PER_FILE! (
set /a FILE_COUNT+=1
set /a LINE_COUNT=1
)
)
if %DRY_RUN%==1 (
echo [ドライラン] 分割ファイル数: %FILE_COUNT%
echo [ドライラン] 1ファイルあたり最大 %LINES_PER_FILE% 行
) else (
echo 分割処理を開始します
)
BOM(バイトオーダーマーク)が1行目の先頭に付いているため、ヘッダー変数に不可視文字が混入します。PowerShellでBOMを除去してから処理するか、最初からBOMなしUTF-8でCSVを保存してください。
:: PowerShellでBOM除去してから処理
powershell -NoProfile -Command "
$content = [System.IO.File]::ReadAllText('data.csv', [System.Text.Encoding]::UTF8)
if ($content.StartsWith([char]0xFEFF)) { $content = $content.Substring(1) }
[System.IO.File]::WriteAllText('data_nobom.csv', $content, [System.Text.Encoding]::UTF8)
Write-Host 'BOM除去完了: data_nobom.csv'
"
これは正常動作です。総行数が分割単位の倍数でない場合、最後のファイルには余りの行数だけが入ります。例えば2500行を1000行ずつ分割すると、3ファイル目は500行になります。全データが確実に含まれているかは総行数を確認してください。
総行数の確認方法:
:: find /c でCSVの行数を確認 find /c /v "" data.csv
for /f は数十万行を超えると処理が極端に遅くなります。100万行超の大容量CSVには方法5のPowerShell(StreamReader/StreamWriter)を使ってください。I/Oが最適化されており、100万行でも数十秒で処理できます。
:: batからPowerShellで100万行超CSVを分割
@echo off
powershell -NoProfile -Command "
$r = [System.IO.StreamReader]::new('huge.csv', [System.Text.Encoding]::UTF8)
$h = $r.ReadLine()
$n = 0; $f = 1
$w = [System.IO.StreamWriter]::new('out_001.csv', $false, [System.Text.Encoding]::UTF8)
$w.WriteLine($h)
while (-not $r.EndOfStream) {
$l = $r.ReadLine(); $n++
if ($n -gt 100000) {
$w.Close(); $f++; $n = 1
$w = [System.IO.StreamWriter]::new(\"out_$($f.ToString('000')).csv\", $false, [System.Text.Encoding]::UTF8)
$w.WriteLine($h)
}
$w.WriteLine($l)
}
$w.Close(); $r.Close()
Write-Host \"完了: $f ファイル\"
"
まとめ
CSVを分割する方法を5つ解説しました。用途に応じて最適な方法を選んでください。
| 目的 | 推奨方法 | ポイント |
|---|---|---|
| シンプルな均等分割 | 方法1(for /f + カウンタ) | 追加ツール不要・コードが短い |
| ヘッダー付き均等分割 | 方法2(ヘッダー保持) | 分割後もそのまま読み込める |
| 月別・地域別に仕分け | 方法3・4(列値判定) | データ内容で自動振り分け |
| 10万行超の大容量 | 方法5(PowerShell) | StreamReader/Writerで高速 |
| RFC4180形式(引用符付き) | PowerShell Import-Csv | 複雑なフィールドに対応 |
少量のCSVには for /f、大容量や日本語・引用符を含む複雑なCSVにはPowerShellを選ぶのがコツです。
バッチファイルでの特定文字列の検索・コピーについてはbatで文字列検索・コピーする方法、ファイル名への日付挿入についてはbatでファイル名に日付を入れる方法も参照してください。

