【bat】バッチファイルで拡張子ごとにファイルを自動仕分けする完全ガイド|サブフォルダ再帰・除外リスト・ドライラン・ログ記録・コピー版まで

【bat】バッチファイルで拡張子ごとにファイルを自動仕分けする完全ガイド|サブフォルダ再帰・除外リスト・ドライラン・ログ記録・コピー版まで bat

ダウンロードフォルダや作業フォルダに画像・PDF・動画が混在して整理できていない、そんなときはバッチファイルで拡張子ごとに自動仕分けできます。%%~xf で拡張子を取得してサブフォルダに振り分けるのが基本ですが、サブフォルダ再帰・除外リスト・ドライラン・ログ記録など実運用で必要な要素も含めて解説します。

この記事でわかること

  • フラットフォルダ内のファイルを拡張子ごとにフォルダへ移動する基本パターン
  • サブフォルダも再帰的にスキャンして仕分ける方法(for /r)
  • 特定の拡張子のみ対象・逆に除外リストで仕分けをスキップする方法
  • 実際に移動する前に内容を確認するドライランモードの実装
  • 移動日時・ファイル数をログファイルに記録する方法
  • 元フォルダを保持したままコピーして仕分けするコピー版
  • PowerShell でより高度な仕分けルールを実装する方法
スポンサーリンク

拡張子仕分けの実装方式比較

方式 対象範囲 ドライラン ログ 実装難度
基本(move) 直下のみ
再帰(for /r) サブフォルダも対象
除外リスト付き 直下 or 再帰
ドライラン付き 選択可 ✅ 確認のみ
ログ記録付き 選択可 ✅ 件数・日時
コピー版(xcopy) 選択可
PowerShell 再帰・複雑条件 ✅ -WhatIf ✅ 詳細
方式の使い分け
初めての仕分けには必ずドライランで確認してから実行してください。大量ファイルを扱う場合や定期実行するならログ記録も組み合わせましょう。サブフォルダ内も一括処理したいなら for /r の再帰版が便利です。

方法1:基本の拡張子仕分け(フラットフォルダ版)

for %%f in (*.*)) でフォルダ直下のすべてのファイルをループし、%%~xf で拡張子を取得してサブフォルダへ move します。仕分け先フォルダが存在しない場合は自動で作成します。

sort_by_ext.bat: フォルダ直下のファイルを拡張子ごとに仕分け(基本版)
@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" のように固定名フォルダに移動する処理を追加してください。
sort_by_ext_samedir.bat: バッチファイルと同じフォルダを仕分け対象にする版
@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 を使うとサブフォルダ内のファイルもすべてスキャンして仕分けできます。深い階層のファイルも一括処理したいときに使います。

sort_recursive.bat: サブフォルダも含めてすべてのファイルを拡張子別に仕分け
@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 は仕分けしない」といった絞り込みが必要な場合のパターンです。

sort_whitelist.bat: 対象拡張子をホワイトリストで指定して仕分け
@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
sort_blacklist.bat: 除外リスト(ブラックリスト)に含まれない拡張子のみ仕分け
@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 を渡すと確認のみ、引数なしで本番実行するパターンです。

sort_dryrun.bat: –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:移動ログを記録して後から確認できるようにする

定期実行する仕分けスクリプトでは、いつ・何件・どのファイルを移動したかをログファイルに記録しておくと、トラブル時の調査に役立ちます。

sort_with_log.bat: タイムスタンプ付きログを出力する仕分けスクリプト
@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:元フォルダを保持したままコピーして仕分けする

移動ではなくコピーで仕分けすることで、元のフォルダ構造を壊さずに仕分け済みフォルダを別途作成できます。バックアップ用途や、仕分け結果を確認してから元ファイルを削除したい場合に便利です。

sort_copy.bat: 元ファイルを残してコピーで拡張子別フォルダに仕分け
@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
xcopy でサブフォルダ再帰コピー版(/s /i オプション)
@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 でドライランも標準対応しています。

PowerShell: 拡張子ごとに仕分け(バッチから呼び出し)
@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: ファイルサイズが1MB以上かつ jpg/png のみ仕分け(複合条件)
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)"
    }
"
PowerShell 版のメリット

  • -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)

Q拡張子が大文字(.JPG)のファイルが jpg フォルダではなく JPG フォルダに入ってしまいます。
Aバッチの文字列比較は大文字小文字を区別します。set "EXT=!EXT!" の後に PowerShell で小文字変換する方法か、if /i(大文字小文字無視)で比較する方法があります。シンプルな対策は仕分け先フォルダ名を作るときにpowershell -Command "'!EXT!'.ToLower()" で小文字化する方法です。PowerShell 版では .ToLower() で統一しているため問題になりません。
Q移動中に「別のプロセスで使用されています」エラーが出て失敗します。
Aファイルが他のアプリで開かれていると move が失敗します。if %ERRORLEVEL% neq 0 でエラーを検出してecho [失敗] %%~nxF >> "%LOGFILE%" でログに残し、バッチはそのまま継続するのが実用的です。後でエラーログを確認して手動で移動してください。
Q仕分け先に同名ファイルがある場合、上書きされますか?
Amove コマンドは同名ファイルが存在すると上書きします。上書きを防ぐには仕分け前に if exist "仕分け先\%%~nxF" で確認して%%RANDOM%% を付加したファイル名に変更して移動してください(方法2の再帰版参照)。または PowerShell 版で -ErrorAction SilentlyContinue + -Force の挙動を使い分けることもできます。
Q定期的に自動実行したいのですがタスクスケジューラに登録できますか?
Aタスクスケジューラへの登録は schtasks /create でできます。例: 毎日 22:00 に実行するなら
schtasks /create /tn "仕分け" /tr "C:\sort.bat" /sc daily /st 22:00
詳しくはPC起動・ログオン時に自動実行する完全ガイドも参照してください。
Q仕分け後にファイル名をリネームしながら整理したい場合はどうすればいいですか?
A仕分けと同時にリネームする場合は move "元パス" "仕分け先\新ファイル名" のようにmove の第2引数に移動先フォルダではなく移動先フルパスを指定します。ファイル名のプレフィックス追加・連番付与・文字列置換などのリネームパターンはファイル名の先頭・末尾に文字を一括追加するバッチファイル名の文字列を一括置換するバッチも参照してください。