空フォルダを削除

2005-05-15

必要性と仕様

ここ数年は、メーラとして EdMax を愛用しています。 EdMax は、添付ファイルをデコードして取り出し、Attachmentフォルダに日時ベースの子フォルダを作成して格納して管理します。

メールを複製したときに添付ファイルが重複しないので使用容量が節約できるというメリットが考えられる仕様ですが、実際のところ複製なんてしないのであまり関係ありません。 むしろウィルス入り添付ファイルを受信した時に、問題のある添付ファイル「だけ」が検閲されるという点がメリットかも知れません。 メール本文と添付ファイルをくっつけた状態で管理していると、ウィルスに関係のない本文や他の添付ファイルまで隔離されてしまい、参照できなくなってしまいます。 メーラによっては、過去のメールまで参照できなくなって慌てる事になるかも。

その一方で、作成した添付ファイルフォルダが消されずに残ってしまう事が多いのです。 そう言う操作をしたからなのか、不具合なのかは分からないのですが。 以前から気にはなっていたので、Attachmentフォルダを整理してみる事にしました。

一般的には、空のフォルダを削除するツールを使って削除するのが簡単かつ確実でしょう。 たとえば DelPath とか。 でもそれでは面白くないので、今回はバッチファイルで頑張ってみる事にします。 今の環境だと、OS は WindowsXP 。 バッチコマンドの拡張機能を使うので、古いヤツでは使えないかも。

単純に空のフォルダを削除してもいいんですが、少しだけ拡張してみます。 具体的には、次のファイルを削除する処理を追加します。

贅沢を言えば、Attach$_01.html といった名前のファイルも削除できると便利なのです。 ただし、内容をチェックして「メール本文が同じ内容だったっら削除」という事が条件になります。 要するに HTMLメールですね、コレは。 そう言うプログラムを作れば可能ですが、今回は Attachment フォルダの整理が目的なので除外しておきます。

また、EdMax の Attachment フォルダの整理が目的なので、フォルダ階層も1階層のみとします。 フォルダの中にフォルダがあって… という構成には対応しません。 具体的な階層イメージは、次のようになります。

EdMax
  └ Attachment
       ├ 20020217_015714_anzye0
       ├ 20020217_015714_e0z5x2
       │          :
       │          :
       ├ 20050512_173011_zm2xch
       └ 20050512_190954_h28jut

フォルダを検索して処理を行なう FOR

MS-DOS では、不特定多数のファイルを処理するには FOR コマンドを使います。 例えばコマンドプロンプトで次のコマンドを実行すると、カレントディレクトリにあるファイル名の一覧が得られます。 セット(*.*)に該当するファイル名を %f に順次適用して、DO 以下のコマンドを繰り返し実行します。 この例では「%f の内容を echo する」という作業を *.* に適合するファイル数だけ繰り返しているわけですね。

for %f in ( *.* ) do @echo %f

やってみれば分かりますが、検索対象は「ファイル」です。 上の例では、サブフォルダは対象外であり、一覧には出てきません。 今回は「フォルダ」を対象とする必要があるので、FOR のコマンド拡張を使い /D スイッチを付ます。 これで、セット内にワイルドカードがある場合にフォルダを検索する様になります。 上の例に /d を追加したものですが、こんどはフォルダの一覧が得られます(逆にファイルの一覧は出てきません)。

for /d %f in ( *.* ) do @echo %f

サブルーチンを呼び出す CALL

あとは、DO 以下のコマンドに「指定されたフォルダ内の不要ファイルを削除した上でファイルの有無をチェックして、空フォルダだったら当該フォルダを削除」という処理を書いてあげます。 こんな長ったらしい処理が1コマンドで書けるわけないので、この内容もバッチファイルになります。

バッチファイルからバッチファイルを呼ぶには、CALL コマンドを使います。 引数は、バッチファイル名かラベル。 バッチファイルがポコポコ出来ても面倒なので、ここはラベルを使うことにしましょうか。

バッチファイルは、ファイルの終端に達すると処理を終了します。 CALL で呼ばれたバッチ処理も同様で、終了後は呼び元の次のコマンドに制御が返ります。 BASIC言語の GOSUB〜RETURN みたいな感じです。

return させるにはファイル終端へ達する必要があるので、GOTO でジャンプします。 たとえば :EndOfFile なんてラベルをバッチファイル終端に用意して、このラベルに GOTO すればいいわけです。 が、ここもコマンド拡張が使え、:EOF というキーワードでファイル終端へ制御を移すことができます。

フォルダ名の一覧とフォルダごとのファイル名の一覧を得るバッチファイルは、次のようになります。 各一覧を得る部分がサブルーチンになっています。

sample code
@echo off
:BatchMain
    echo Folder List
    for /d %%d in ( *.* ) do call :ShowFolderName %%d
    
    echo/
    echo File List
    for /d %%d in ( *.* ) do call :ShowFolderFiles "%%d"
    
    goto :EOF

:ShowFolderName
    echo %1
    goto :EOF

:ShowFolderFiles
    echo ^<%~1^>
    for %%f in ( *.* ) do echo %%f
    goto :EOF

空ファイルの削除

サイズが 0Byte のファイルを削除するには、ファイルのサイズを得る必要が有ります。 実は、変数参照の置換も拡張されています。 %~zI 構文を使うと、I がファイルのサイズに展開されます。 I は、フォルダ名(FOR /D で取得)と ファイル名( FOR で取得)を "\" で繋げば得られます。

引数 %1 でファイル名を受け取り、サイズが 0 でなければそのまま return 。 サイズが 0 であれば当該ファイルを削除して return という処理にすればよいでしょう。

sample code
:delempty
    if not %~z1==0 goto :EOF
    echo delete empty file : %~1
    del %1
    goto :EOF

空フォルダの判定

空フォルダを削除するには、指定されたフォルダの中にファイルがあるか否かを判断すればよい事になります。 前提条件としてフォルダ中にサブフォルダは存在しないので、ここでは無視します。 フォルダ中のファイルについては、FOR で得ることができます。 あとは判断の方法でしょう。

ここでは、環境変数を使います。 チェック用の環境変数、たとえば list を用意して、見つかったファイル名を足して行きます。 FOR が終わった時に環境変数 list の内容が空であればファイルがない、すなわち空フォルダと判断出来ます。

sample code
:chkfolder
    set list=
    for %%f in ( %1\*.* ) do set list=%list% %%f
    if "%list%"=="" rmdir %1
    goto :EOF

一見良さそうに見えますが、実は list に入るのは最後に見つかったファイル名のみです。 ファイルの一覧を作りたかったのに。 これでも目的は達成できるのですが、思ったとおりに動かないのは問題です。

これは環境変数の展開が FOR の開始時に1度だけ行われる事に原因があります。 コマンド拡張では遅延環境変数の展開がサポートされており、set list=!list! %%f のように記述すればファイルの一覧が得られます。 ただし、コマンドインタープリタの /V:ON スイッチで遅延環境展開を有効にしなければなりません。

面倒なので、ここもサブルーチンにしてしまいます。 環境変数 list の展開が FOR コマンドの外で行われるため、この様な問題は発生しません。

sample code
:chkfolder
    set list=
    for %%f in ( %1\*.* ) do call :addname %%f
    if "%list%"=="" rmdir %1
    goto :EOF

:addname
    set list=%list% %1
    goto :EOF

cleaning.bat

以上をふまえ作成した、空フォルダを削除するバッチファイルです。

上記以外で注意すべき点として、添付ファイル名は空白を含んでいる可能性があるという点くらいです。 ファイル名は二重引用符(")で囲む必要が有ります。 引用符を除去するには、変数参照の %~I 構文を用います。

コマンド拡張を使えば、MS-DOS 時代にはプログラムを作るしかなかった事が幾らか出来るようになります。 けっこう拡張されているので、色々遊べるかも知れませんね。

cleaning.bat
@echo off
rem -----------------------------------
rem メイン処理
rem -----------------------------------
    for /D %%d in ( 2* ) do call :deluseless %%d
    for /D %%d in ( 2* ) do call :chkfolder %%d
    goto :EOF


rem -----------------------------------
rem 不要ファイルを削除
rem -----------------------------------
:deluseless
    for %%f in ( "%1\*.*" ) do call :delempty "%%f"
    for %%f in ( "%1\Norton AntiVirus が削除しました*.txt" ) do call :delfile "%%f"
    goto :EOF


rem -----------------------------------
rem 空ファイルを削除
rem -----------------------------------
:delempty
    if not %~z1==0 goto :EOF
    echo delete empty file : %~1
    del %1
    goto :EOF


rem -----------------------------------
rem ファイルを削除
rem -----------------------------------
:delfile
    echo delete file : %~1
    del %1
    goto :EOF


rem -----------------------------------
rem 各フォルダをチェック
rem -----------------------------------
:chkfolder
    rem 指定フォルダ内のファイル名一覧を得る
    set list=
    for %%f in ( %1\*.* ) do call :addname "%%f"
    
    rem ファイルが存在しないならフォルダを削除
    if "%list%"=="" call :delfolder %1
    
    goto :EOF


rem -----------------------------------
rem 環境変数 list にファイル名を追加
rem -----------------------------------
:addname
    if "%list%"=="" goto first

:later
    set list=%list% %~1
    goto :EOF

:first
    set list=%~1
    goto :EOF


rem -----------------------------------
rem フォルダを削除
rem -----------------------------------
:delfolder
    echo delete folder : %~1
    rmdir %1
    goto :EOF
Copyright© 1998-2006 Hira