複数の CSV ファイルを1つにまとめる作業は、月次レポートの集計・ログ統合・データ前処理などでよく発生します。バッチファイルで自動化すれば、毎月の手作業を数秒で済ませることができます。
この記事では CSV 結合の全パターン を体系的に解説します。単純な全行結合から「ヘッダーを1回だけ保持する」「サブフォルダも再帰検索する」「出力ファイル自身を除外する」「ファイル名を列として追加する」といった実務的なパターンまで網羅します。
- copy コマンドで最速結合する方法(ヘッダーあり)
- ヘッダーを1行だけ保持して結合する方法
- サブフォルダを再帰的に検索して結合する方法(for /r)
- 出力ファイル自身が結合対象に混入しないようにする方法
- 結合元ファイル名を列として追加する方法(トレーサビリティ)
- 文字コード(Shift-JIS・UTF-8)混在への対処
- 重複行の除去(sort /unique)
- PowerShell との連携で複雑な結合を処理する方法
- 落とし穴5選・実践例3本・FAQ6問
- 1. 最速結合:copy コマンドでヘッダーごと全行を結合
- 2. ヘッダーを1行だけ保持して結合する(最重要パターン)
- 3. サブフォルダを再帰的に検索して結合(for /r)
- 4. 特定フォルダのみ・ファイル名パターンで絞り込んで結合
- 5. 結合元ファイル名を列として追加する(トレーサビリティ)
- 6. 出力ファイルの末尾に余計な改行が入る問題への対処
- 7. 文字コード(Shift-JIS・UTF-8)混在への対処
- 8. 重複行を除去しながら結合(sort /unique)
- 9. 差分結合(既存の combined.csv に新しいファイルだけ追記する)
- 10. 落とし穴5選と対策
- 11. 実践例3本
- 12. まとめ:CSV結合方法の使い分け表
- FAQ
1. 最速結合:copy コマンドでヘッダーごと全行を結合
最もシンプルな方法です。すべてのファイルのヘッダーも含めて全行を連結します。ヘッダーが重複しても問題なく、後でフィルタリングする用途に向いています。
@echo off :: 同一フォルダ内のすべての CSV を combined.csv に結合 copy *.csv combined.csv echo [OK] combined.csv に結合しました
copy コマンドはバイナリコピーではなく、*.csv のようなワイルドカードを使うとテキストとして連結されます(改行も保持されます)。ただし各ファイルのヘッダーがすべて含まれるため、ヘッダーを1行にしたい場合は次節の方法を使ってください。
:: copy コマンドの注意点 :: - combined.csv が *.csv に含まれる場合、自己参照のループが起きることがある :: → 出力ファイルを別フォルダに指定するか、拡張子を変えて回避 copy *.csv D:outputcombined.csv :: + 演算子で明示的に列挙する方法 copy jan.csv + feb.csv + mar.csv Q1.csv
リダイレクト(>/>>)の詳細は リダイレクトの使い方完全ガイド も参照してください。
2. ヘッダーを1行だけ保持して結合する(最重要パターン)
実務で最もよく使うパターンです。1ファイル目のヘッダーのみを出力し、2ファイル目以降はヘッダー行をスキップして結合します。
2-1. 基本実装
@echo off
setlocal enabledelayedexpansion
set "OUT=combined.csv"
set "FIRST=1"
:: 出力ファイルを初期化
if exist "%OUT%" del "%OUT%"
for %%F in (*.csv) do (
:: 出力ファイル自身をスキップ
if /i not "%%F"=="%OUT%" (
if "!FIRST!"=="1" (
:: 1ファイル目: ヘッダーを含めすべての行を出力
type "%%F" >> "%OUT%"
set "FIRST=0"
) else (
:: 2ファイル目以降: ヘッダー(1行目)をスキップして出力
for /f "usebackq skip=1 tokens=* delims=" %%L in ("%%F") do (
echo %%L >> "%OUT%"
)
)
)
)
echo [OK] 結合完了 → %OUT%
2-2. 処理件数をカウントしながら結合
@echo off
setlocal enabledelayedexpansion
set "OUT=combined.csv"
set "FIRST=1"
set "FILE_COUNT=0"
set "ROW_COUNT=0"
if exist "%OUT%" del "%OUT%"
for %%F in (*.csv) do (
if /i not "%%F"=="%OUT%" (
set /a FILE_COUNT+=1
if "!FIRST!"=="1" (
type "%%F" >> "%OUT%"
set "FIRST=0"
) else (
for /f "usebackq skip=1 tokens=* delims=" %%L in ("%%F") do (
echo %%L >> "%OUT%"
set /a ROW_COUNT+=1
)
)
)
)
:: 総行数取得(ヘッダー含む)
set "TOTAL=0"
for /f %%C in ('find /c /v "" ^< "%OUT%"') do set "TOTAL=%%C"
echo [OK] !FILE_COUNT! ファイルを結合しました
echo 出力行数(ヘッダー含む): !TOTAL! 行 → %OUT%
遅延展開が必要な理由は setlocal enabledelayedexpansion 完全ガイド を参照してください。
3. サブフォルダを再帰的に検索して結合(for /r)
複数のサブフォルダに分散した CSV をまとめて1ファイルに結合するパターンです。FOR文の使い方完全ガイド も参照してください。
@echo off
setlocal enabledelayedexpansion
set "SRC_DIR=C:workcsv"
set "OUT=C:workcombined_all.csv"
set "FIRST=1"
set "FILE_COUNT=0"
if exist "%OUT%" del "%OUT%"
:: for /r でサブフォルダを再帰検索
for /r "%SRC_DIR%" %%F in (*.csv) do (
:: 出力ファイル自身をスキップ
if /i not "%%~fF"=="%OUT%" (
set /a FILE_COUNT+=1
echo 処理中: %%~nxF (%%~dpF)
if "!FIRST!"=="1" (
type "%%F" >> "%OUT%"
set "FIRST=0"
) else (
for /f "usebackq skip=1 tokens=* delims=" %%L in ("%%F") do (
echo %%L >> "%OUT%"
)
)
)
)
if "!FIRST!"=="1" (
echo [WARN] CSV ファイルが見つかりませんでした: %SRC_DIR%
exit /b 1
)
echo [OK] !FILE_COUNT! 件のCSVを結合しました → %OUT%
4. 特定フォルダのみ・ファイル名パターンで絞り込んで結合
「2024年のファイルだけ」「sales_で始まるファイルだけ」のように結合対象を絞り込むパターンです。
@echo off
setlocal enabledelayedexpansion
set "OUT=sales_combined.csv"
set "FIRST=1"
if exist "%OUT%" del "%OUT%"
:: "sales_" で始まる CSV だけを結合
for %%F in (sales_*.csv) do (
if /i not "%%F"=="%OUT%" (
if "!FIRST!"=="1" (
type "%%F" >> "%OUT%"
set "FIRST=0"
) else (
for /f "usebackq skip=1 tokens=* delims=" %%L in ("%%F") do (
echo %%L >> "%OUT%"
)
)
)
)
:: 複数のパターンをORで結合(2024_ または 2025_ で始まるファイル)
for %%P in (2024_*.csv 2025_*.csv) do (
if /i not "%%P"=="%OUT%" echo 対象: %%P
)
5. 結合元ファイル名を列として追加する(トレーサビリティ)
どの元ファイルのデータかを追跡できるよう、各行にファイル名の列を追加して結合するパターンです。
@echo off
setlocal enabledelayedexpansion
set "OUT=combined_with_source.csv"
set "FIRST=1"
if exist "%OUT%" del "%OUT%"
for %%F in (*.csv) do (
if /i not "%%F"=="%OUT%" (
if "!FIRST!"=="1" (
:: ヘッダー行に source_file 列を追加
for /f "usebackq tokens=* delims=" %%H in ("%%F") do (
echo %%H,source_file >> "%OUT%"
goto :header_done
)
:header_done
set "FIRST=0"
)
:: データ行に ファイル名(拡張子なし)を追加
for /f "usebackq skip=1 tokens=* delims=" %%L in ("%%F") do (
echo %%L,%%~nF >> "%OUT%"
)
)
)
echo [OK] ファイル名列付きで結合しました → %OUT%
6. 出力ファイルの末尾に余計な改行が入る問題への対処
echo %%L >> file は各行の末尾に改行が追加されますが、ファイル末尾に余分な空行が入ることがあります。また type コマンドを使うとこの問題を回避できます。
@echo off
setlocal enabledelayedexpansion
set "OUT=combined.csv"
set "FIRST=1"
if exist "%OUT%" del "%OUT%"
for %%F in (*.csv) do (
if /i not "%%F"=="%OUT%" (
if "!FIRST!"=="1" (
:: type は改行を余分に追加しないため、1ファイル目は type を使う
type "%%F" >> "%OUT%"
set "FIRST=0"
) else (
:: 2ファイル目以降も可能な限り type + findstr でヘッダー除外
:: skip=1 の for /f より type + findstr /v の方が高速な場合がある
more +1 "%%F" >> "%OUT%"
)
)
)
echo [OK] 結合完了 → %OUT%
more +1 "%%F" は1行目をスキップしてファイル内容を出力するシンプルな方法です。ただし more コマンドは大きなファイルでは遅いため、大量データには for /f skip=1 を使いましょう。
7. 文字コード(Shift-JIS・UTF-8)混在への対処
異なる文字コードのCSVを結合すると文字化けが発生します。PowerShell に委譲するのが最も確実な解決策です。バッチでUnicodeファイルを扱えない原因と解決策 も参照してください。
7-1. すべてのファイルが同じ文字コード(Shift-JIS)の場合
:: Shift-JIS の CSV は copy コマンドや for /f でそのまま結合できる
@echo off
setlocal enabledelayedexpansion
set "OUT=combined.csv"
set "FIRST=1"
if exist "%OUT%" del "%OUT%"
for %%F in (*.csv) do (
if /i not "%%F"=="%OUT%" (
if "!FIRST!"=="1" ( type "%%F" >> "%OUT%" & set "FIRST=0"
) else ( more +1 "%%F" >> "%OUT%" )
)
)
echo [OK] 結合完了
7-2. UTF-8 のCSVを結合する(PowerShell使用)
@echo off
:: PowerShell で UTF-8 CSV を結合(ヘッダー1行保持)
powershell -NoProfile -Command "
$files = Get-ChildItem -Path C:workcsv -Filter *.csv | Where-Object { $_.Name -ne "combined.csv" }
$first = $true
foreach ($f in $files) {
$lines = Get-Content -Path $f.FullName -Encoding UTF8
if ($first) {
$lines | Out-File -FilePath C:workcombined.csv -Encoding UTF8
$first = $false
} else {
$lines | Select-Object -Skip 1 | Out-File -FilePath C:workcombined.csv -Encoding UTF8 -Append
}
}
Write-Host "[OK] 結合完了"
"
PowerShellをバッチファイルから呼び出す方法 も参照してください。
8. 重複行を除去しながら結合(sort /unique)
複数ファイルに同一行が存在する場合(ログファイルの重複エントリなど)、重複を除去しながら結合できます。テキストファイルをソートする方法完全ガイド も参照してください。
@echo off
setlocal enabledelayedexpansion
set "OUT=combined.csv"
set "TMP=%TEMP%combined_tmp_%RANDOM%.csv"
set "FIRST=1"
if exist "%OUT%" del "%OUT%"
:: まず通常通り全ファイルを結合(ヘッダー1回保持)
for %%F in (*.csv) do (
if /i not "%%F"=="%OUT%" (
if "!FIRST!"=="1" ( type "%%F" >> "%OUT%" & set "FIRST=0"
) else ( more +1 "%%F" >> "%OUT%" )
)
)
:: ヘッダーを保存
for /f "usebackq tokens=* delims=" %%H in ("%OUT%") do (
set "HEADER=%%H"
goto :got_header
)
:got_header
:: ヘッダーを除いたデータ行を sort /unique で重複除去
more +1 "%OUT%" | sort /unique > "%TMP%"
:: ヘッダー + 重複除去済みデータ を最終ファイルに書き出し
echo !HEADER! > "%OUT%"
type "%TMP%" >> "%OUT%"
del "%TMP%"
echo [OK] 重複除去済みで結合しました → %OUT%
9. 差分結合(既存の combined.csv に新しいファイルだけ追記する)
毎日新しいCSVが追加されるケースで、前回処理済みのファイルは再処理せず差分のみ追記するパターンです。
@echo off
setlocal enabledelayedexpansion
set "OUT=combined.csv"
set "PROCESSED_LOG=processed_files.txt"
:: 出力ファイルが存在しない場合は初回結合として扱う
if not exist "%OUT%" (
set "FIRST=1"
) else (
set "FIRST=0"
)
set "NEW_COUNT=0"
for %%F in (*.csv) do (
if /i not "%%F"=="%OUT%" (
:: 処理済みログに記録されているファイルはスキップ
set "ALREADY="
if exist "%PROCESSED_LOG%" (
findstr /x /i /c:"%%F" "%PROCESSED_LOG%" >nul 2>&1
if not errorlevel 1 set "ALREADY=1"
)
if not defined ALREADY (
set /a NEW_COUNT+=1
if "!FIRST!"=="1" (
type "%%F" >> "%OUT%"
set "FIRST=0"
) else (
more +1 "%%F" >> "%OUT%"
)
:: 処理済みとして記録
echo %%F >> "%PROCESSED_LOG%"
echo [追加] %%F
)
)
)
if !NEW_COUNT! EQU 0 (
echo [SKIP] 新しい CSV ファイルはありません
) else (
echo [OK] !NEW_COUNT! ファイルを追記しました → %OUT%
)
10. 落とし穴5選と対策
落とし穴1:出力ファイル自身が結合対象に含まれる
:: NG: combined.csv が *.csv に含まれてしまい、自分自身を読み込む
for %%F in (*.csv) do (
more +1 "%%F" >> combined.csv :: ← combined.csv が対象に含まれる
)
:: OK: 出力ファイル自身をスキップする条件を追加
for %%F in (*.csv) do (
if /i not "%%F"=="combined.csv" (
more +1 "%%F" >> combined.csv
)
)
:: または出力先を別フォルダに指定する
for %%F in (*.csv) do (
more +1 "%%F" >> "D:outputcombined.csv"
)
落とし穴2:echo でリダイレクトすると行末に余分なスペースが入る
:: NG: echo %%L >> file は %%L の後にスペースが入ることがある
for /f "tokens=* delims=" %%L in (data.csv) do (
echo %%L >> combined.csv
)
:: OK1: リダイレクトをループの外に出す(パフォーマンスも向上)
(
for /f "tokens=* delims=" %%L in (data.csv) do (
echo %%L
)
) >> combined.csv
:: OK2: more +1 を使う(for /f より高速な場合がある)
more +1 "data.csv" >> combined.csv
変数の末尾に余計な空白が入ってしまうときの対策 も参照してください。
落とし穴3:ループ内でフラグを更新すると %変数% では古い値になる
:: NG: %FIRST% は for ループ進入時に評価されるため常に "1" になる
set "FIRST=1"
for %%F in (*.csv) do (
if "%FIRST%"=="1" ( type "%%F" >> out.csv ) else ( more +1 "%%F" >> out.csv )
set "FIRST=0"
)
:: OK: setlocal enabledelayedexpansion + !FIRST! を使う
setlocal enabledelayedexpansion
set "FIRST=1"
for %%F in (*.csv) do (
if "!FIRST!"=="1" ( type "%%F" >> out.csv ) else ( more +1 "%%F" >> out.csv )
set "FIRST=0"
)
変数展開が動かない原因と修正方法 も参照してください。
落とし穴4:for /f が ; 始まりの行をスキップする(eol の問題)
:: for /f はデフォルトで ; 始まりの行を読み飛ばす
:: CSV にセミコロンで始まるフィールドがある場合に行が欠落する
:: 対策: eol= に使わない文字を指定してスキップを無効化
for /f "usebackq eol=| skip=1 tokens=* delims=" %%L in ("data.csv") do (
echo %%L >> combined.csv
)
落とし穴5:for /r は出力ファイルのフルパスと比較しないと除外できない
:: NG: for /r のループ変数 %%F はフルパスになる
:: "combined.csv" だけと比較してもフルパスとは一致しない
set "OUT=combined.csv"
for /r "C:work" %%F in (*.csv) do (
if /i not "%%F"=="%OUT%" ( :: ← これでは除外できない
more +1 "%%F" >> "%OUT%"
)
)
:: OK: 出力ファイルのフルパスを変数に格納して比較
set "OUT_FULL=C:workcombined.csv"
for /r "C:work" %%F in (*.csv) do (
if /i not "%%~fF"=="%OUT_FULL%" (
more +1 "%%F" >> "%OUT_FULL%"
)
)
11. 実践例3本
実践例1:月次売上CSVを四半期ファイルに結合する
毎月生成される売上CSVを四半期ごとのファイルに自動結合するスクリプトです。
@echo off
setlocal enabledelayedexpansion
set "BASE_DIR=C:worksales"
set "OUT_DIR=C:workquarterly"
if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"
:: Q1(1〜3月)・Q2(4〜6月)・Q3(7〜9月)・Q4(10〜12月)
for %%Q in (Q1 Q2 Q3 Q4) do (
set "OUT=%OUT_DIR%%%Q_combined.csv"
set "FIRST=1"
if exist "!OUT!" del "!OUT!"
if "%%Q"=="Q1" set "MONTHS=01 02 03"
if "%%Q"=="Q2" set "MONTHS=04 05 06"
if "%%Q"=="Q3" set "MONTHS=07 08 09"
if "%%Q"=="Q4" set "MONTHS=10 11 12"
for %%M in (!MONTHS!) do (
set "SRC=%BASE_DIR%sales_2024%%M.csv"
if exist "!SRC!" (
if "!FIRST!"=="1" (
type "!SRC!" >> "!OUT!"
set "FIRST=0"
) else (
more +1 "!SRC!" >> "!OUT!"
)
echo [追加] sales_2024%%M.csv → %%Q_combined.csv
) else (
echo [SKIP] sales_2024%%M.csv が見つかりません
)
)
if "!FIRST!"=="0" (
echo [OK] %%Q 完了 → !OUT!
) else (
echo [WARN] %%Q の CSV が1件もありませんでした
)
)
実践例2:複数システムのログCSVをサブフォルダから再帰収集して統合する
@echo off
setlocal enabledelayedexpansion
set "LOG_ROOT=C:worklogs"
set "OUT=C:workall_logs.csv"
set "FIRST=1"
set "FILE_COUNT=0"
if exist "%OUT%" del "%OUT%"
echo ===== ログCSV統合開始: %date% %time% =====
for /r "%LOG_ROOT%" %%F in (log_*.csv) do (
if /i not "%%~fF"=="%OUT%" (
set /a FILE_COUNT+=1
if "!FIRST!"=="1" (
:: ヘッダーにソース列を追加
for /f "usebackq eol=| tokens=* delims=" %%H in ("%%F") do (
echo %%H,source_system >> "%OUT%"
goto :hdr_done
)
:hdr_done
set "FIRST=0"
)
:: データ行にシステム名(上位フォルダ名)を追加
for /f "usebackq eol=| skip=1 tokens=* delims=" %%L in ("%%F") do (
echo %%L,%%~dpF >> "%OUT%"
)
echo [追加] %%~nxF
)
)
set "TOTAL=0"
for /f %%C in ('find /c /v "" ^< "%OUT%"') do set "TOTAL=%%C"
echo ===== 完了: !FILE_COUNT! ファイル / !TOTAL! 行 → %OUT% =====
実践例3:PowerShell で UTF-8 CSV を結合して重複除去・ソートまで行う
文字コードが混在する環境や、大量ファイルを高速処理したい場合に有効です。
@echo off
echo CSV結合処理を開始します...
powershell -NoProfile -ExecutionPolicy Bypass -Command "
$srcDir = "C:workcsv"
$outFile = "C:workcombined_final.csv"
$exclude = [System.IO.Path]::GetFileName($outFile)
$files = Get-ChildItem -Path $srcDir -Filter "*.csv" -Recurse | Where-Object { $_.Name -ne $exclude } | Sort-Object Name
if ($files.Count -eq 0) { Write-Error "CSV が見つかりません"; exit 1 }
$allRows = @()
$header = $null
foreach ($f in $files) {
$rows = Import-Csv -Path $f.FullName -Encoding UTF8
if ($header -eq $null) { $header = ($rows | Select-Object -First 1).PSObject.Properties.Name }
$allRows += $rows
}
# 重複除去・ソート
$result = $allRows | Sort-Object ($header[0]) -Unique
$result | Export-Csv -Path $outFile -Encoding UTF8 -NoTypeInformation
Write-Host "[OK] $($result.Count) 行(重複除去後)→ $outFile"
"
if errorlevel 1 (
echo [ERROR] PowerShell 処理に失敗しました
exit /b 1
)
echo [完了]
12. まとめ:CSV結合方法の使い分け表
| 方法 | ヘッダー制御 | 再帰対応 | 文字コード | 向いているケース |
|---|---|---|---|---|
copy *.csv out.csv |
全ファイルのヘッダーが混入 | × | Shift-JIS | ヘッダー不要な単純結合 |
| for ループ + type/more +1 | 1行保持可 | for /r で可 | Shift-JIS | 標準的な業務CSV結合 |
| for /r 再帰 | 1行保持可 | ◎ | Shift-JIS | サブフォルダ分散CSV |
| PowerShell Get-Content | 1行保持可 | Recurse対応 | UTF-8対応 | UTF-8 CSV・大量ファイル |
| PowerShell Import-Csv | 自動管理 | Recurse対応 | UTF-8対応 | 重複除去・ソート・加工が必要な場合 |
CSV 関連の他の記事として バッチファイルでCSVファイルを読み込む方法完全ガイド・CSVファイルを分割する方法完全ガイド・バッチファイルでCSVを結合する方法完全ガイド もあわせて参照してください。
FAQ
FIRST を使い、最初のファイルは type で全行出力し、2ファイル目以降は more +1 または for /f skip=1 でヘッダーをスキップして追記します(→ 2節)。if /i not "%%F"=="combined.csv" の条件でスキップするか、出力先を別フォルダに指定してください。for /r を使う場合はフルパス変数と %%~fF で比較する必要があります(→ 落とし穴5)。for /f は Shift-JIS で処理するため UTF-8 CSV は文字化けします。PowerShell の Get-Content -Encoding UTF8 または Import-Csv -Encoding UTF8 で処理してください(→ 7節)。for /r "フォルダ" %%F in (*.csv) do でサブフォルダを再帰検索できます。ただし出力ファイルのスキップにはフルパス比較が必要です(→ 3節・落とし穴5)。sort /unique でソート・重複除去し、ヘッダーと再結合します(→ 8節)。複雑な条件での重複除去は PowerShell の Sort-Object -Unique を推奨します。echo %%L,%%~nF >> out.csv のように %%~nF(ファイル名部分)をカンマ区切りで追記します。ヘッダー行には source_file などの列名を追加してください(→ 5節)。
