新しいn個のファイルを残して、それ以外を消すというありがちな処理

パフォーマンスカウンタのログファイル。常時とるようにして何かあったら調べられるようにしたんだけど、そうなると問題になるのは垂れ流しにされるログファイルの後始末。ディスクのサイズが大きくなったとはいえ、放置というのもどうなの? ということで直近のn個を残して他を削除するナイスな方法はないものか?と思った次第。

結局使ったのはバッチスクリプト。こんなのになった。

@ECHO OFF
SET KEEPFILES=%3
SET TARGETDIR=%1
SET TARGETFILES=%2
IF "%KEEPFILES%"=="" SET KEEPFILES=3
SET COUNTER=0
FOR /F %%I IN ('DIR %TARGETDIR%%TARGETFILES% /O-D /A-D /B') DO CALL :DELFILES %TARGETDIR%%%I
EXIT /B

:DELFILES
SET /A COUNTER+=1
IF %COUNTER% LEQ %KEEPFILES% ECHO %COUNTER% KEEP %1
IF NOT %COUNTER% LEQ %KEEPFILES% ECHO %COUNTER% DELETE %1
EXIT /B

第一引数に対象のディレクトリ名。お尻は\で閉じてほしい。
第二引数は対象となるファイルをワイルドカードで指定したもの。
第三引数はいくつ保持するか。
いきなりファイル削除して泣いちゃったら困るのでこのスクリプトはECHOで対象になったかどうかだけを表示している。安全装置は自己責任で外すこと! おじさんとの約束だ!


で、これで動いておしまい。以下余談。というかおもしろかった所。

このスクリプト、カウンタをインクリメントしながら削除対象かどうかを判定している部分をわざわざサブルーチンにしている。DOの中にカッコでブロック切ってそこにかけばいいのに。具体的にはこんな風に。

FOR /F %%I IN ('DIR %TARGETDIR%%TARGETFILES% /O-D /A-D /B') DO (
  SET /A COUNTER+=1
  IF %COUNTER% LEQ %KEEPFILES% ECHO %COUNTER% KEEP %TARGETDIR%%%I
  IF NOT %COUNTER% LEQ %KEEPFILES% ECHO %COUNTER% %TARGETDIR%%%I
)
EXIT /B

これが意図したとおりには動かない。なんでか知らないけどCOUNTERの値がECHOで見るとゼロのまんま。なんでえええええぇぇぇええぇえぇえええええhfjfkじぇlkじぇflk;じぇkl??????? ってなりました。

これがバッチスクリプトの癖で変数を%%で括るとその時の変数の値で展開されて、そこから実際に実行されるんだけど、さて、このDOのブロック。%COUNTER%は一体いつ展開されるのかというと、SET /A COUNTER=+1を実行する前。この前の段階でゼロそのものに%COUNTER%と書かれている所が全部置換されてから実行される!

…まあ、そんなことはSET /?すると書いてあって、私もそれで解決したんだけどね。

set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%

この例では、現在のディレクトリのファイルの一覧は作成されず、代わりに最後
に見つけられたファイルが LIST 変数に設定されます。
これは %LIST% が FOR 文が読み取られるとき、
一度だけ展開され、そのときは LIST 変数が空だからです。
つまり、実際に実行されている FOR ループは

for %i in (*) do set LIST= %i

で、LIST に最後に見つけられたファイルを設定し続けているだけです。

で、Vオプションの話になるんだけど、私の場合これは使えない!と思ったので別の方法で回避。それがあのサブルーチンコールだったわけです。

ということで超ひさびさの日記でした。そしてゲーム用PCのHDDが壊れた。OSはSSDに入れてあって、HDD外したらふつうに起動したんだけど。さて、どうしてくれよう。