ダウンロードフォルダや作業フォルダに画像・PDF・動画が混在して整理できていない、そんなときはバッチファイルで拡張子ごとに自動仕分けできます。%%~xf で拡張子を取得してサブフォルダに振り分けるのが基本ですが、サブフォルダ再帰・除外リスト・ドライラン・ログ記録など実運用で必要な要素も含めて解説します。
- フラットフォルダ内のファイルを拡張子ごとにフォルダへ移動する基本パターン
- サブフォルダも再帰的にスキャンして仕分ける方法(for /r)
- 特定の拡張子のみ対象・逆に除外リストで仕分けをスキップする方法
- 実際に移動する前に内容を確認するドライランモードの実装
- 移動日時・ファイル数をログファイルに記録する方法
- 元フォルダを保持したままコピーして仕分けするコピー版
- PowerShell でより高度な仕分けルールを実装する方法
拡張子仕分けの実装方式比較
| 方式 | 対象範囲 | ドライラン | ログ | 実装難度 |
|---|---|---|---|---|
| 基本(move) | 直下のみ | ✕ | ✕ | 低 |
| 再帰(for /r) | サブフォルダも対象 | ✕ | ✕ | 低 |
| 除外リスト付き | 直下 or 再帰 | △ | ✕ | 中 |
| ドライラン付き | 選択可 | ✅ 確認のみ | ✕ | 中 |
| ログ記録付き | 選択可 | ✕ | ✅ 件数・日時 | 中 |
| コピー版(xcopy) | 選択可 | ✕ | △ | 中 |
| PowerShell | 再帰・複雑条件 | ✅ -WhatIf | ✅ 詳細 | 高 |
初めての仕分けには必ずドライランで確認してから実行してください。大量ファイルを扱う場合や定期実行するならログ記録も組み合わせましょう。サブフォルダ内も一括処理したいなら
for /r の再帰版が便利です。方法1:基本の拡張子仕分け(フラットフォルダ版)
for %%f in (*.*)) でフォルダ直下のすべてのファイルをループし、%%~xf で拡張子を取得してサブフォルダへ move します。仕分け先フォルダが存在しない場合は自動で作成します。
@echo off
setlocal enabledelayedexpansion
REM ===== 設定 =====
set "SRC=C:\Users\%USERNAME%\Downloads"
set "DEST=C:\sorted"
REM ================
if not exist "%DEST%" mkdir "%DEST%"
set COUNT=0
for %%F in ("%SRC%\*.*") do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
REM 拡張子を小文字に統一(大文字小文字混在対策)
for %%E in (!EXT!) do set "EXT=%%E"
if not exist "%DEST%\!EXT!" mkdir "%DEST%\!EXT!"
move "%%~fF" "%DEST%\!EXT!\" >nul 2>&1
if !ERRORLEVEL! equ 0 (
set /a COUNT+=1
echo [移動] %%~nxF → !EXT!\
) else (
echo [失敗] %%~nxF
)
)
)
echo.
echo 完了: %COUNT% 件のファイルを仕分けしました
endlocal
%%~xF が空文字("")のファイルは拡張子なしファイルです。これらを除外するために if not "%%~xF"=="" の条件を入れています。拡張子なしファイルも仕分けしたい場合は set "EXT=no_ext" のように固定名フォルダに移動する処理を追加してください。@echo off
setlocal enabledelayedexpansion
REM バッチファイル自身があるフォルダを仕分け対象にする
set "SRC=%~dp0"
REM 末尾の \ を除去
set "SRC=%SRC:~0,-1%"
set COUNT=0
for %%F in ("%SRC%\*.*") do (
REM バッチファイル自身はスキップ
if /i not "%%~xF"==".bat" (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
if not exist "%SRC%\!EXT!" mkdir "%SRC%\!EXT!"
move "%%~fF" "%SRC%\!EXT!\" >nul
set /a COUNT+=1
)
)
)
echo %COUNT% 件を仕分けしました
endlocal
%%~dp0 でバッチ自身の場所を仕分け対象にする%~dp0 はバッチファイルのドライブ+ディレクトリパスを返します。ダウンロードフォルダに置いてダブルクリックするだけで仕分け実行できる使い捨てスクリプトを作るときに便利です。/i not "%%~xF"==".bat" でバッチファイル自身の移動を防いでいます。拡張子の取得方法の詳細はファイルの拡張子を取得する方法も参照してください。方法2:サブフォルダも含めて再帰的に仕分けする
for /r を使うとサブフォルダ内のファイルもすべてスキャンして仕分けできます。深い階層のファイルも一括処理したいときに使います。
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\work\messy"
set "DEST=C:\work\sorted"
if not exist "%DEST%" mkdir "%DEST%"
set COUNT=0
for /r "%SRC%" %%F in (*.*) do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
REM 仕分け先フォルダがない場合は作成
if not exist "%DEST%\!EXT!" mkdir "%DEST%\!EXT!"
REM 同名ファイルが仕分け先に存在する場合のリネーム
set "DESTFILE=%DEST%\!EXT!\%%~nxF"
if exist "!DESTFILE!" (
set "DESTFILE=%DEST%\!EXT!\%%~nF_%%RANDOM%%%%~xF"
)
move "%%~fF" "!DESTFILE!" >nul
set /a COUNT+=1
echo [移動] %%~nxF → !EXT!\
)
)
echo 完了: %COUNT% 件
endlocal
サブフォルダに同じ名前のファイルが複数ある場合、単純に
move すると上書きされます。if exist "仕分け先" で存在チェックして%%RANDOM%%(0〜32767のランダム値)をファイル名に付加することで衝突を避けられます。完全なファイル移動パターンはワイルドカードを使ってファイルを移動する方法も参照してください。方法3:特定の拡張子のみ対象・除外リストで仕分けをスキップする
すべての拡張子ではなく「jpg・png・gif のみ仕分けする」や「bat・ini・log は仕分けしない」といった絞り込みが必要な場合のパターンです。
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\Users\%USERNAME%\Downloads"
set "DEST=C:\sorted"
REM 仕分け対象の拡張子リスト(スペース区切り)
set "WHITELIST=jpg jpeg png gif mp4 mov pdf docx xlsx pptx zip"
if not exist "%DEST%" mkdir "%DEST%"
for %%F in ("%SRC%\*.*") do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
REM ホワイトリストに含まれるか確認
set "MATCH="
for %%W in (%WHITELIST%) do (
if /i "!EXT!"=="%%W" set "MATCH=1"
)
if defined MATCH (
if not exist "%DEST%\!EXT!" mkdir "%DEST%\!EXT!"
move "%%~fF" "%DEST%\!EXT!\" >nul
echo [移動] %%~nxF → !EXT!\
) else (
echo [スキップ] %%~nxF (対象外)
)
)
)
endlocal
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\work"
set "DEST=C:\work\sorted"
REM 仕分け除外リスト(バッチ・設定・ログ等)
set "BLACKLIST=bat cmd ini log txt cfg lnk"
if not exist "%DEST%" mkdir "%DEST%"
for %%F in ("%SRC%\*.*") do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
set "SKIP="
for %%B in (%BLACKLIST%) do (
if /i "!EXT!"=="%%B" set "SKIP=1"
)
if not defined SKIP (
if not exist "%DEST%\!EXT!" mkdir "%DEST%\!EXT!"
move "%%~fF" "%DEST%\!EXT!\" >nul
)
)
)
echo 仕分け完了
endlocal
方法4:ドライランで移動内容を事前確認する
大量のファイルを一括移動する前に、実際には移動せず「何をどこへ移動するか」を確認するドライランモードは必ず実装しておきましょう。引数 --dry-run を渡すと確認のみ、引数なしで本番実行するパターンです。
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\Users\%USERNAME%\Downloads"
set "DEST=C:\sorted"
REM ドライランフラグ判定
set "DRYRUN="
if /i "%~1"=="--dry-run" set "DRYRUN=1"
if defined DRYRUN (
echo ========================================
echo [ドライランモード] 実際には移動しません
echo ========================================
)
set COUNT=0
set SKIP=0
for %%F in ("%SRC%\*.*") do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
set "DESTDIR=%DEST%\!EXT!"
if defined DRYRUN (
echo [予定] %%~nxF → !DESTDIR!\
set /a COUNT+=1
) else (
if not exist "!DESTDIR!" mkdir "!DESTDIR!"
move "%%~fF" "!DESTDIR!\" >nul
if !ERRORLEVEL! equ 0 (
echo [移動] %%~nxF → !EXT!\
set /a COUNT+=1
) else (
echo [失敗] %%~nxF
set /a SKIP+=1
)
)
)
)
if defined DRYRUN (
echo.
echo 移動予定: %COUNT% 件 (ドライランのため実際には移動していません)
echo 本番実行するには引数なしで実行してください
) else (
echo 移動完了: %COUNT% 件 / 失敗: %SKIP% 件
)
endlocal
- 初めてそのフォルダに仕分けスクリプトを実行するとき
- ホワイトリスト・ブラックリストの設定が正しいか確認するとき
- 仕分け先フォルダ構造をあらかじめ把握しておきたいとき
ドライラン確認後に本番実行することで、意図しないファイルの移動を防げます。
方法5:移動ログを記録して後から確認できるようにする
定期実行する仕分けスクリプトでは、いつ・何件・どのファイルを移動したかをログファイルに記録しておくと、トラブル時の調査に役立ちます。
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\Users\%USERNAME%\Downloads"
set "DEST=C:\sorted"
set "LOGDIR=C:\logs\sort"
REM ログファイルを日付別に作成
for /f "tokens=1-3 delims=/" %%A in ("%DATE%") do set "LOGDATE=%%A%%B%%C"
set "LOGFILE=%LOGDIR%\%LOGDATE%.log"
if not exist "%DEST%" mkdir "%DEST%"
if not exist "%LOGDIR%" mkdir "%LOGDIR%"
echo [%DATE% %TIME%] ===== 仕分け開始 ===== >> "%LOGFILE%"
set COUNT=0
set FAIL=0
for %%F in ("%SRC%\*.*") do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
if not exist "%DEST%\!EXT!" mkdir "%DEST%\!EXT!"
move "%%~fF" "%DEST%\!EXT!\" >nul 2>&1
if !ERRORLEVEL! equ 0 (
set /a COUNT+=1
echo [OK] [%TIME%] %%~nxF → !EXT!\ >> "%LOGFILE%"
) else (
set /a FAIL+=1
echo [NG] [%TIME%] %%~nxF (移動失敗) >> "%LOGFILE%"
)
)
)
echo [%DATE% %TIME%] 完了: 移動=%COUNT%件 失敗=%FAIL%件 >> "%LOGFILE%"
echo 仕分け完了: %COUNT% 件(ログ: %LOGFILE%)
endlocal
ログを日付別ファイルに分けることで1ファイルが肥大化するのを防げます。古いログを自動削除するには
forfiles /p "%LOGDIR%" /d -30 /c "cmd /c del @file" で30日以前のログを削除する処理を末尾に追加してください。ログの日付別ファイル保存の詳細はログを日付別ファイルに自動保存する完全ガイドも参照してください。方法6:元フォルダを保持したままコピーして仕分けする
移動ではなくコピーで仕分けすることで、元のフォルダ構造を壊さずに仕分け済みフォルダを別途作成できます。バックアップ用途や、仕分け結果を確認してから元ファイルを削除したい場合に便利です。
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\work\originals"
set "DEST=C:\work\sorted_copy"
if not exist "%DEST%" mkdir "%DEST%"
set COUNT=0
for %%F in ("%SRC%\*.*") do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
if not exist "%DEST%\!EXT!" mkdir "%DEST%\!EXT!"
copy "%%~fF" "%DEST%\!EXT!\" >nul
set /a COUNT+=1
echo [コピー] %%~nxF → !EXT!\
)
)
echo 完了: %COUNT% 件をコピーしました
endlocal
@echo off
setlocal enabledelayedexpansion
set "SRC=C:\work\originals"
set "DEST=C:\work\sorted_copy"
if not exist "%DEST%" mkdir "%DEST%"
for /r "%SRC%" %%F in (*.*) do (
if not "%%~xF"=="" (
set "EXT=%%~xF"
set "EXT=!EXT:~1!"
if not exist "%DEST%\!EXT!" mkdir "%DEST%\!EXT!"
copy "%%~fF" "%DEST%\!EXT!\" >nul
)
)
echo コピー仕分け完了
endlocal
コピー成功を確認してから元ファイルを削除することで、移動中断による半端な状態を防げます。
copy が成功(%ERRORLEVEL%==0)した場合のみ del するパターンが安全です。ファイルコピーの詳細はファイルをコピーする方法完全ガイドも参照してください。方法7:PowerShell でより高度な仕分けルールを実装する
PowerShell の Get-ChildItem + Move-Item を使うと、拡張子だけでなくファイルサイズ・更新日時・ファイル名のパターンなど複合条件での仕分けが簡潔に書けます。-WhatIf でドライランも標準対応しています。
@echo off
setlocal
set "SRC=C:\Users\%USERNAME%\Downloads"
set "DEST=C:\sorted"
REM ドライランなら --whatif を渡す
set "WHATIF="
if /i "%~1"=="--dry-run" set "WHATIF=-WhatIf"
powershell -NoProfile -ExecutionPolicy Bypass -Command "
$src = '%SRC%'
$dest = '%DEST%'
$whatif = '%WHATIF%'
Get-ChildItem -Path $src -File | ForEach-Object {
$ext = $_.Extension.TrimStart('.').ToLower()
if ($ext -eq '') { $ext = 'no_ext' }
$destDir = Join-Path $dest $ext
if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir | Out-Null }
if ($whatif) {
Write-Host "[予定] $($_.Name) -> $ext\\"
} else {
Move-Item -Path $_.FullName -Destination $destDir -Force
Write-Host "[移動] $($_.Name) -> $ext\\"
}
}
"
endlocal
powershell -NoProfile -ExecutionPolicy Bypass -Command "
$src = 'C:\work'
$dest = 'C:\sorted_large_images'
Get-ChildItem -Path $src -File -Recurse |
Where-Object { $_.Length -gt 1MB -and $_.Extension -match '\.(jpg|jpeg|png)$' } |
ForEach-Object {
$destDir = Join-Path $dest $_.Extension.TrimStart('.').ToLower()
if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir | Out-Null }
Move-Item -Path $_.FullName -Destination $destDir
Write-Host "[移動] $($_.Name) ($([math]::Round($_.Length/1MB,1)) MB)"
}
"
-Recurseでサブフォルダ再帰が1行で完結Where-Objectでサイズ・日時・名前パターンを自在に組み合わせToLower()で拡張子の大文字小文字を統一(JPG と jpg を同じ扱い)- 拡張子なしファイルを
no_extフォルダに自動振り分け - -WhatIf 相当のドライランを変数で制御可能
まとめ
- 基本版:
for %%F in (*.*)+%%~xFで拡張子取得 →mkdir+moveで仕分け - 再帰版:
for /rでサブフォルダも一括処理。同名ファイルは%%RANDOM%%でリネームして衝突回避 - ホワイトリスト/ブラックリスト:
for %%W in (%WHITELIST%)でリスト照合してスキップ判定 - ドライラン:
--dry-run引数で確認モード。大量ファイルの一括操作前に必ず確認 - ログ記録: 日付別ログファイルに移動件数・失敗件数・タイムスタンプを記録
- コピー版:
copyで元ファイルを残して仕分け。成功確認後に元ファイル削除で安全移動 - PowerShell: 複合条件(サイズ・日時・名前)での高度な仕分けが可能
関連記事: ワイルドカードを使ってファイルを移動する方法 / ファイルの拡張子を一括変換する方法 / ファイルの拡張子を取得する方法
よくある質問(FAQ)
set "EXT=!EXT!" の後に PowerShell で小文字変換する方法か、if /i(大文字小文字無視)で比較する方法があります。シンプルな対策は仕分け先フォルダ名を作るときにpowershell -Command "'!EXT!'.ToLower()" で小文字化する方法です。PowerShell 版では .ToLower() で統一しているため問題になりません。move が失敗します。if %ERRORLEVEL% neq 0 でエラーを検出してecho [失敗] %%~nxF >> "%LOGFILE%" でログに残し、バッチはそのまま継続するのが実用的です。後でエラーログを確認して手動で移動してください。move コマンドは同名ファイルが存在すると上書きします。上書きを防ぐには仕分け前に if exist "仕分け先\%%~nxF" で確認して%%RANDOM%% を付加したファイル名に変更して移動してください(方法2の再帰版参照)。または PowerShell 版で -ErrorAction SilentlyContinue + -Force の挙動を使い分けることもできます。schtasks /create でできます。例: 毎日 22:00 に実行するならschtasks /create /tn "仕分け" /tr "C:\sort.bat" /sc daily /st 22:00詳しくはPC起動・ログオン時に自動実行する完全ガイドも参照してください。
move "元パス" "仕分け先\新ファイル名" のようにmove の第2引数に移動先フォルダではなく移動先フルパスを指定します。ファイル名のプレフィックス追加・連番付与・文字列置換などのリネームパターンはファイル名の先頭・末尾に文字を一括追加するバッチやファイル名の文字列を一括置換するバッチも参照してください。

