もっといい方法がありそうだけど思いついてちょっと面白かったので備忘録。
目的
- ファイルが同じ階層に不定期に増えていくので、その総数を100に保つ
- 100を超えたら古いファイルから削除する
# 最初
content/ -+- 001.txt
`- 002.txt
# 総数が100を超える
content/ -+- 001.txt # <- 削除
|- 002.txt
|...
|- 100.txt
`- 101.txt
# 総数が103のとき
content/ -+- 101.txt # <- 削除
|- 102.txt # <- 削除
|- 103.txt # <- 削除
|- 104.txt
|...
|- 202.txt
`- 203.txt
これをするスクリプトを毎日1回cronで実行する。
方策
lsしてtailしてrmに渡す。
rm `ls -t content/* | tail -n+101`
これだけだとファイルが100を超えていないときrmに何も渡されずエラーになるので、渡すファイルがあるか確認する。
[ -n "`ls -t content/* | tail -n+101`" ] && rm -v `ls -t content/* | tail -n+101`
if test -n "`ls -t content/* | tail -n+101`";then
rm -v `ls -t content/* | tail -n+101`
fi
解説
rm `ls -t content/* | tail -n+101`
バッククォートの中身は次。
- 「ls -t」: ファイルを更新日時順に表示(下が古い)
- 「tail -n+101」: 101行目から末行までを表示(頭行から100行目までカット)
これでrmに「『100番目に新しいファイル』より古いファイル」を渡せる。つまり最新100個だけ残して古いものを削除する。
ちなみにバッククオートの外側をダブルクォートしてはいけない。してしまうと引数が1つに固まってしまい、「rm: `content/002.txt\ncontent/001.txt' を削除できません: そのようなファイルやディレクトリはありません」とか言われる。
「content」ではなく「content/*」とするのは、lsの出力が
- 前者では「001.txt 002.txt ...」
とファイル名だけなのに対して
- 後者では「content/001.txt content/002.txt ...」
と相対パスが含まるので、rmにそのまま渡せるから。
[ -n "`ls -t content/* | tail -n+101`" ] && rm `ls -t content/* | tail -n+101`
- 「[ -n "`~`" ]」: 「[」コマンド。角括弧の中に条件式を入れる。「-n」は「文字列の長さが0より大きければ真」つまり「101個以上ファイルが存在したら真」となる
- 「&&」: 直前の式が真のとき次の式を実行する
角括弧の内側はそれぞれ空白を空けなければならない。これは「[」がコマンドだからという理由らしい。
条件式の部分ではバッククォートの外側をダブルクォートしなければならない。しないと、常に真が返ったりする*1。
if test -n "`ls -t content/* | tail -n+101`"; then
rm `ls -t content/* | tail -n+101`
fi
- 「test -n "`~`"」: 角括弧と代替可能なtestコマンド*2
結び
やることがワンラインで書けるならそのままcronに書けて便利。ただし「&&」はOKだけど「;」は使えないので、いくら1行に書いたからといってifが使えるというわけではない。
あとcronの実行環境は全然違ったりするので環境変数はじめ色々気をつける点が多い。「bash -l コマンド」とか書くとbash環境下で実行してくれる。