バッチファイルでフォルダ間を移動するとき、「どこへ移動したか」「何を移動したか」を後から追えるようにしたい場面があります。本記事では pushd/popd によるディレクトリスタック管理から、move コマンドの実行ログ記録、robocopy /MOV /LOG による詳細ログ付き移動まで、実務で使える「履歴付きフォルダ間移動」パターンを体系的に解説します。
この記事でできること
pushd/popdでディレクトリ移動履歴をスタック管理- スタックを使って作業前のフォルダに自動復帰
moveコマンドの結果をログファイルに日時付きで記録robocopy /MOV /LOGで詳細ログ付きの安全な移動- 移動履歴を CSV 形式で蓄積して後から参照
- 実践例3本(作業フォルダ往復・CSV移動履歴・robocopy ログローテーション)と落とし穴・FAQ も解説
履歴付き移動の方法比較
「フォルダ間移動」と「ファイルの移動」では記録する内容が異なります。用途に合わせて使い分けましょう。
| 目的 | 方法 | 履歴の種類 | 特徴 |
|---|---|---|---|
| カレントディレクトリの移動履歴 | pushd/popd |
スタック(メモリ上) | スクリプト終了で消える。popd で自動復帰 |
| ファイル移動の実行ログ | move + echo>> |
テキストファイル | シンプル。移動ファイル名・日時を記録 |
| 詳細ログ付きファイル移動 | robocopy /MOV /LOG |
テキストファイル | コピー後削除で安全。速度・件数も記録 |
| 後から検索できる移動履歴 | CSV 形式ログ | CSV ファイル | Excel や grep で分析可能 |
方法1: pushd/popd でディレクトリ移動をスタック管理
pushd はカレントディレクトリをスタックに積んでから指定フォルダへ移動します。popd はスタックから取り出して元のフォルダへ戻ります。サブルーチン的な作業に最適です。
基本: 1段の pushd/popd
@echo off setlocal echo 作業前のフォルダ: %CD% rem 現在地をスタックに積んで移動 pushd "C:\work\data" echo 作業中のフォルダ: %CD% rem ここで作業 dir /b *.csv rem スタックから取り出して元のフォルダへ復帰 popd echo 復帰後のフォルダ: %CD% endlocal
ネスト: 複数フォルダを順番に移動して戻る
pushd を重ねると LIFO(後入れ先出し)スタックに積まれます。popd を同じ回数呼べば逆順で元の場所に戻ります。
@echo off setlocal rem 起点(スタックの底) echo [START] %CD% pushd "C:\project\src" echo [1] %CD% rem ... ソース処理 ... pushd "C:\project\dist" echo [2] %CD% rem ... ビルド成果物処理 ... pushd "C:\project\logs" echo [3] %CD% rem ... ログ確認 ... rem 逆順に戻る popd echo [back to 2] %CD% popd echo [back to 1] %CD% popd echo [back to START] %CD% endlocal
スタックは「積み重ねた皿」のイメージです。pushd するたびに現在地が皿として積まれ、popd するたびに一番上の皿(最後に積んだ場所)を取り出します。スタックはスクリプトの実行中のみ有効で、endlocal または スクリプト終了で消去されます。
方法2: pushd の便利な応用(相対パス・UNC パス・一時移動)
pushd は UNC パス(\\server\share)への移動も可能です。この場合 Windows が自動で仮想ドライブを割り当てます。
スクリプト自身のフォルダに一時移動する
スクリプトと同じフォルダにあるファイルを相対パスで扱いたい場合に便利です。
@echo off
setlocal
rem %~dp0 はスクリプト自身が置かれているフォルダ
pushd "%~dp0"
rem ここでは相対パスがスクリプトフォルダ基準になる
if exist "config.ini" (
echo config.ini を読み込みます
rem ... 処理 ...
) else (
echo config.ini が見つかりません
)
popd
endlocal
UNC パス(ネットワーク共有)への移動
@echo off
setlocal
rem UNC パスへの pushd は仮想ドライブ(Z: 等)が自動割り当てされる
pushd "\\server01\share\data"
if errorlevel 1 (
echo [エラー] ネットワーク共有に接続できませんでした。
goto END
)
echo 接続先: %CD%
rem ... ネットワーク越しの処理 ...
popd
rem popd で仮想ドライブも自動解除される
:END
endlocal
方法3: move コマンドの結果をログファイルに記録
ファイルを移動するたびに日時・ファイル名・移動先をログファイルに追記します。後から「いつ・何を・どこへ移動したか」を確認できます。
基本: 移動ごとにログ追記
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\work\upload"
set "DST=C:\work\done"
set "LOG=C:\logs\move_history.log"
if not exist "%DST%" mkdir "%DST%"
if not exist "C:\logs" mkdir "C:\logs"
set /a COUNT=0
for %%F in ("%SRC%\*.csv") do (
move "%%F" "%DST%\"
if not errorlevel 1 (
set /a COUNT+=1
echo %DATE% %TIME:~0,8% MOVED %%~nxF %SRC% -^> %DST% >> "%LOG%"
) else (
echo %DATE% %TIME:~0,8% ERROR %%~nxF >> "%LOG%"
)
)
echo 移動完了: !COUNT! 件 ログ: %LOG%
endlocal
%DATE% と %TIME% はOSのロケール設定によって形式が異なります。(例: 日本語環境では 2024/03/15、英語環境では Fri 03/15/2024)ログを後から grep・ソートしやすくするには wmic や PowerShell でISO 8601形式(yyyy-MM-dd)のタイムスタンプを取得することをお勧めします。
PowerShell でISO形式タイムスタンプを取得してログに記録
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\work\input"
set "DST=C:\work\archive"
set "LOG=C:\logs\move_history.log"
if not exist "%DST%" mkdir "%DST%"
rem ISO形式タイムスタンプ取得(ロケール非依存)
for /f "usebackq tokens=*" %%T in (`powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-dd HH:mm:ss'"`) do set "TS=%%T"
for %%F in ("%SRC%\*.*") do (
move "%%F" "%DST%\"
if not errorlevel 1 (
echo !TS! MOVED "%%~nxF" "%SRC%" -> "%DST%" >> "%LOG%"
)
)
echo 完了
endlocal
方法4: robocopy /MOV /LOG で安全な移動と詳細ログ
robocopy の /LOG オプションは移動した全ファイルの詳細をファイルに記録します。/MOV(コピー後削除)と組み合わせると詳細ログ付きの安全な移動が実現できます。
基本: /MOV /LOG で移動しながらログ記録
@echo off setlocal set "SRC=C:\data\raw" set "DST=C:\data\archive" set "LOG=C:\logs\robocopy_move.log" rem /MOV: ファイルを移動(コピー後に元ファイル削除) rem /LOG+: ログをファイルに追記(+ で追記モード) rem /TS: ファイルのタイムスタンプをログに記録 rem /FP: ログにフルパスを記録 robocopy "%SRC%" "%DST%" *.* /MOV /LOG+:"%LOG%" /TS /FP /NJH /NJS echo 移動完了。ログ: %LOG% endlocal
日付別ログファイルに分けて記録
ログファイルを日付ごとに分けると、後から特定の日の移動を参照しやすくなります。
@echo off setlocal set "SRC=C:\upload\pending" set "DST=C:\upload\done" set "LOG_DIR=C:\logs\move" if not exist "%LOG_DIR%" mkdir "%LOG_DIR%" rem 今日の日付を ISO形式で取得 for /f "usebackq tokens=*" %%D in (`powershell -NoProfile -Command "Get-Date -Format 'yyyyMMdd'"`) do set "TODAY=%%D" set "LOG=%LOG_DIR%\move_%TODAY%.log" robocopy "%SRC%" "%DST%" *.* /MOV /LOG+:"%LOG%" /TS /FP echo 完了。ログ: %LOG% endlocal
robocopy の詳しい使い方は bat の robocopy 完全ガイドも合わせて参照してください。
方法5: CSV 形式で移動履歴を蓄積
移動ログを CSV 形式にしておくと、Excel や検索ツールで後から分析できます。ヘッダー行・クォート・カンマを適切に扱う実装例です。
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\data\input"
set "DST=C:\data\done"
set "CSV=C:\logs\move_history.csv"
if not exist "%DST%" mkdir "%DST%"
rem CSV ヘッダー(初回のみ)
if not exist "%CSV%" (
echo datetime,filename,src,dst,status > "%CSV%"
)
rem タイムスタンプ取得
for /f "usebackq tokens=*" %%T in (`powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-dd HH:mm:ss'"`) do set "TS=%%T"
set /a OK=0
set /a NG=0
for %%F in ("%SRC%\*.*") do (
move "%%F" "%DST%\"
if not errorlevel 1 (
echo "!TS!","%%~nxF","%SRC%","%DST%","OK" >> "%CSV%"
set /a OK+=1
) else (
echo "!TS!","%%~nxF","%SRC%","%DST%","ERROR" >> "%CSV%"
set /a NG+=1
)
)
echo 成功: !OK! 件 失敗: !NG! 件
echo 履歴CSV: %CSV%
endlocal
実践例A: 複数の作業フォルダを pushd で往来しながら処理
ビルド処理などで複数フォルダを行き来する際、pushd/popd でスタック管理すると元の場所に確実に戻れます。
@echo off setlocal set "PROJ=C:\myproject" echo ===== ビルド処理開始 ===== rem スクリプト起点から src へ移動 pushd "%PROJ%\src" echo [src] コンパイル処理... rem ↓ ここに実際のコンパイルコマンドを記述(例: call build.bat) echo (ビルド処理: build.bat を呼び出す等) rem dist へ移動してファイルを整理 pushd "%PROJ%\dist" echo [dist] 成果物の整理... if not exist "release" mkdir "release" rem ↓ ここに成果物の移動処理(例: move *.exe release\) echo (成果物を release フォルダへ振り分け) rem テストフォルダへ pushd "%PROJ%\test" echo [test] テスト実行... rem ↓ ここにテスト実行コマンドを記述(例: call run_tests.bat) echo (テストスクリプトを実行) rem 全部戻る popd echo 戻り先: %CD% popd echo 戻り先: %CD% popd echo 戻り先: %CD% echo ===== ビルド処理完了 ===== endlocal
実践例B: 処理済みファイルをフォルダ間移動して CSV に履歴記録
入力フォルダから処理済みフォルダへファイルを移動し、成功・失敗をすべて CSV ログに記録します。毎日実行するバッチ処理の監査ログとして活用できます。
@echo off
setlocal enabledelayedexpansion
set "BASE=C:\etl"
set "INPUT=%BASE%\input"
set "DONE=%BASE%\done"
set "ERROR_DIR=%BASE%\error"
set "CSV=%BASE%\logs\transfer_history.csv"
for %%D in ("%DONE%" "%ERROR_DIR%" "%BASE%\logs") do (
if not exist %%D mkdir %%D
)
rem CSV ヘッダー初回
if not exist "%CSV%" echo datetime,file,result,dest > "%CSV%"
for /f "usebackq tokens=*" %%T in (`powershell -NoProfile -Command "Get-Date -Format 'yyyy-MM-dd HH:mm:ss'"`) do set "TS=%%T"
for %%F in ("%INPUT%\*.csv") do (
rem ファイルサイズが 0 ならエラー扱い(引用符で囲んで安全に比較)
if "%%~zF"=="0" (
move "%%F" "%ERROR_DIR%\"
echo "!TS!","%%~nxF","SKIP_EMPTY","%ERROR_DIR%" >> "%CSV%"
) else (
rem 処理(ここに実処理を記述)
move "%%F" "%DONE%\"
echo "!TS!","%%~nxF","OK","%DONE%" >> "%CSV%"
)
)
echo 処理完了。履歴: %CSV%
endlocal
特定の文字列を含むファイルだけを選んで移動する方法は bat で特定の文字列を含むファイルをコピーする方法も合わせてご覧ください。
実践例C: robocopy で本番→アーカイブへ移動 + ログローテーション
本番フォルダに溜まったログを毎月アーカイブへ移動し、robocopy のログ自体も世代管理するパターンです。
@echo off setlocal set "PROD_LOG=C:\app\logs" set "ARCHIVE=C:\archive\app_logs" set "RLOG_DIR=C:\logs\robocopy" if not exist "%ARCHIVE%" mkdir "%ARCHIVE%" if not exist "%RLOG_DIR%" mkdir "%RLOG_DIR%" rem 今月のアーカイブフォルダを作成 for /f "usebackq tokens=*" %%M in (`powershell -NoProfile -Command "Get-Date -Format 'yyyyMM'"`) do set "YM=%%M" set "DEST=%ARCHIVE%\%YM%" if not exist "%DEST%" mkdir "%DEST%" rem 今日の robocopy ログファイル for /f "usebackq tokens=*" %%D in (`powershell -NoProfile -Command "Get-Date -Format 'yyyyMMdd'"`) do set "TODAY=%%D" set "RLOG=%RLOG_DIR%\move_%TODAY%.log" rem 30日以上前の .log を月別アーカイブへ移動 robocopy "%PROD_LOG%" "%DEST%" *.log /MOV /MINAGE:30 /LOG+:"%RLOG%" /TS /FP /NJH rem 古い robocopy ログ(90日以上)を削除 forfiles /p "%RLOG_DIR%" /m *.log /d -90 /c "cmd /c del @path" >nul 2>&1 echo アーカイブ完了: %DEST% echo ログ: %RLOG% endlocal
ログローテーションの詳細は bat のログローテーション完全ガイドも合わせて参照してください。
よくある落とし穴
落とし穴1: pushd の回数と popd の回数が合わない
pushd より popd が多いと「スタックが空」エラーになります。逆に pushd より popd が少ないと作業後も元のフォルダに戻れません。ネストが深い場合は :CLEANUP ラベルにまとめた popd を書くと管理しやすくなります。
@echo off pushd "C:\a" pushd "C:\b" rem 処理中にエラーが起きても必ず cleanup を通るように if errorlevel 1 goto CLEANUP rem ... 処理 ... :CLEANUP popd popd echo 元のフォルダ: %CD%
落とし穴2: ログファイルのパスにスペースが含まれるとリダイレクトが失敗する
echo ... >> C:\My Logs\history.log のようにスペース含みのパスにリダイレクトするとエラーになります。パスは常にダブルクォートで囲んでください。
rem NG: スペースがあると失敗 echo %DATE% MOVED file.txt >> C:\My Logs\history.log rem OK: ダブルクォートで囲む echo %DATE% MOVED file.txt >> "C:\My Logs\history.log"
落とし穴3: robocopy の終了コードは 0 以外でも正常
robocopy は成功時でもコピー件数に応じて終了コード 1〜7 を返します。if errorlevel 8 以上を失敗と判定してください。
robocopy "%SRC%" "%DST%" *.* /MOV /LOG+:"%LOG%"
rem robocopy 終了コード: 0=変更なし 1=コピー成功 2〜7=部分成功 8以上=エラー
if errorlevel 8 (
echo [エラー] robocopy が失敗しました。終了コード: %ERRORLEVEL%
exit /b 1
)
echo robocopy 正常終了
落とし穴4: move は上書きを確認しない(移動先に同名ファイルがあると即上書き)
ログを append(>>)で追記しているときに移動先に同名ファイルがあると上書きされてしまいます。ログを消さないためにも移動先での重複チェックを入れましょう。
for %%F in ("%SRC%\*.*") do (
if exist "%DST%\%%~nxF" (
echo %DATE% SKIP "%%~nxF" already exists in %DST% >> "%LOG%"
) else (
move "%%F" "%DST%\"
echo %DATE% MOVED "%%~nxF" to %DST% >> "%LOG%"
)
)
落とし穴5: CSV ログのファイル名にカンマが含まれると列がずれる
ファイル名や日時フィールドが "..." でクォートされていれば問題ありませんが、クォートを省略するとファイル名にカンマが混入したときに CSV の列がずれます。必ずダブルクォートで囲んで出力してください。
rem NG: クォートなし(ファイル名にカンマがあると列がずれる) echo %DATE%,%FNAME%,%DST%,OK >> "%CSV%" rem OK: 全フィールドをダブルクォートで囲む echo "%DATE%","%FNAME%","%DST%","OK" >> "%CSV%"
よくある質問(FAQ)
cmd セッションが終了すると pushd スタックも消滅します。次回の cmd 起動時にはデフォルトのカレントディレクトリに戻ります。setlocal を使っている場合は endlocal でもスタックがクリアされます。
日付別ファイルに書き出し + 古いファイルを forfiles /d -N で定期削除するのが定番です。または robocopy /LOG(上書き)と /LOG+(追記)を使い分けて1日1ファイル管理にする方法もあります。
rem 30日以上前のログを削除 forfiles /p "C:\logs" /m *.log /d -30 /c "cmd /c del @path" >nul 2>&1
move は同一ドライブ内ならディレクトリエントリの変更のみで瞬時に完了しますが、別ドライブ間はコピー+削除になります。robocopy /MOV は常にコピー後削除のため別ドライブでも安全で、ログ記録・リトライ・/XO(古いファイルのみ)などの制御オプションが豊富です。
テキストログなら findstr、CSV なら PowerShell の Import-Csv で検索できます。
rem テキストログから特定ファイルの移動履歴を検索
findstr /i "report_2024" "C:\logs\move_history.log"
rem CSV から PowerShell で検索
powershell -NoProfile -Command "Import-Csv 'C:\logs\move_history.csv' | Where-Object { $_.filename -like '*report*' }"
CSV ログの status が “ERROR” の行だけを抽出して再処理するスクリプトが有効です。Out-File -Encoding UTF8NoBOM(PowerShell 6以降)を使うことでBOM なし UTF-8 で出力され、for /f での読み取りが安定します。
@echo off
setlocal
rem CSV から ERROR 行を抽出してファイル名リストを作成
set "CSV=C:\logs\move_history.csv"
set "RETRY_LIST=C:\logs\retry_list.txt"
rem ERROR 行だけ抽出(2列目のファイル名を取得)
powershell -NoProfile -Command ^
"Import-Csv '%CSV%' | Where-Object { $_.result -eq 'ERROR' } | ^
Select-Object -ExpandProperty file | Out-File '%RETRY_LIST%' -Encoding UTF8NoBOM"
rem リストのファイルを再移動
for /f "usebackq tokens=*" %%F in ("%RETRY_LIST%") do (
if exist "C:\data\input\%%F" (
move "C:\data\input\%%F" "C:\data\done\"
)
)
endlocal
まとめ
| 目的 | 推奨方法 | ポイント |
|---|---|---|
| カレントディレクトリの移動履歴管理 | pushd/popd | スタックで自動復帰。ネスト可。スクリプト終了でクリア |
| UNC パス・相対パスへの移動 | pushd + %~dp0 | スクリプト自身のフォルダ基準で相対パスが使える |
| ファイル移動の実行ログ | move + echo>> ログ | ISO形式タイムスタンプに PowerShell を活用 |
| 詳細ログ付き安全な移動 | robocopy /MOV /LOG+ | 終了コード 8以上をエラー判定。上書き制御も可 |
| 後から検索できる移動履歴 | CSV 形式ログ | 全フィールドをダブルクォートでくくる |
| ログファイル肥大化防止 | 日付別ファイル + forfiles 削除 | 90日以上前のログを定期クリーン |
ディレクトリ間の移動管理には pushd/popd、ファイルの移動ログ記録にはシンプルさ重視なら move + echo>>、信頼性重視なら robocopy /MOV /LOG を選ぶのが基本方針です。
ワイルドカードを使ったファイルの一括移動は bat でワイルドカードを使ってファイルを移動する方法完全ガイドを、robocopy の詳細オプションは bat の robocopy 完全ガイドも合わせてご覧ください。

