結局findしたファイルを消す場合はどんなコマンドを使うか
ファイルの更新時間契機なんかでfindして絞り込んだファイルを消したいケースは結構あるけど、結構ページが乱立していて無邪気にコマンド作ると遅くなったりする。
なので、結局どうすりゃいいのかを検証して、まとめてみた。
日付で絞り込む、ファイル名で検索する、といったオプションは様々な方が言及しているのでここでは割愛する。
どんなとき?
findコマンド一発で検索できる対象を、何も考えずに全部削除したい。
削除対象は数百~数千以上に膨らむ可能性がある。そんなに実行時間かけたくない。
今のところの結論
- findの
-exec rm {} \;
は性能面で非推奨 - 対応している環境ならfindの
-delete
オプションで消せばいい - 上記オプションが対応してなくてもfindの
-exec rm {} +
で対応可 - 上記も対応していない環境ならfind結果をパイプして
xargs rm
※ xargsの場合、特殊ケースのファイル名など利用には注意が必要(後述)
なんでそうなるの?は下記の検証結果から。
2020/03/25:xargs rm
の記述変更
色々なケースを検証してみた
実行方式はいくつか考えられるので、パターン分けて検証。
検証環境
- OS:Windows10 Professional
- Shell:Git for Windows付属のGit Bash(なので、Linux環境でやったら結果変わるかも)
- 検証方式:空ファイルをtouchで1000個作って、find経由で削除する。find時はtimeコマンドで実行時間計測 & 比較
尚、削除対象のファイルは下記のコマンドで作成。
touch fooooooooooooooooo_{1..1000}.txt
検証結果
無邪気に検索対象をワイルドカードにして-exec rmに噛ませる
$ time find *.txt -exec rm -f {} \; real 0m37.532s user 0m4.074s sys 0m16.459s
一応動いてくれるのだが、通常findコマンドの第1引数は検索先のディレクトリを指定するはずなので、あまりこの形式で打たないほうがいい気がする。
※ そもそも*.txtが1件も存在しないとエラーになるし。
そしてファイル1000件で30秒オーバー。めっちゃ遅い。
カレントディレクトリを検索対象にして-exec rmに噛ませる
$ time find . -type f -exec rm -f {} \; real 0m37.080s user 0m3.640s sys 0m15.960s
相変わらずファイル1000件で30秒以上かかる。ワイルドカード方式とそう変わらず遅い。
そもそもこの方式が遅いのはfind結果1件1件に対してrmコマンドが発行されるからである。
ファイルが1000件対象になると、上記方式ではrmコマンドが1000回実行される。
性能を考慮すると検索結果に対して一括でコマンド発行する方式が推奨になる。
-exec rmの実行を+で実行する方式にしてみる
$ time find . -type f -exec rm -f {} + real 0m0.346s user 0m0.046s sys 0m0.249s
実行時間は比べ物にならないほど短くなる。
ただし、この方式での実行はfindした結果全件を1つのrmコマンドファイルの引数に打ち込むことになる。
ファイル数が極端に多くなったときにArgument list too long
でエラーを引くリスクがある。
修正:find自身がxargsと同等の機能を備えていて、引数超過にならないように自動的に調整してくれる認識。
find結果をxargsに噛ませる
$ time find . -type f | xargs rm -f real 0m0.376s user 0m0.030s sys 0m0.357s
xargsが引数をOS上限にかからないように分割してくれる(はず)なので、これもArgument list too long
を引かなくて済む。
ただし、スペースやメタ文字を含むファイル名など、xargsを噛ませることで異常が発生するケースもあるので注意。
linux - How to use "xargs" properly when argument list is too long - Stack Overflow
オバフロさんは-execの方がマシだって言ってるけどそういう問題でもないような。
-exec {} +
ならMAX_ARGSを意識した上で、特殊文字含むファイル名に対しても適切に操作してくれるみたいなので、オバフロさんの言う通りxargs噛ませる必要性が薄いならそちらに任せたほうが良いのだろう。
まとめ
環境が対応していて用途に合うのであれば-delete一択で良いのではないか。
参考にしたページにも書かれているのだけど、findの-deleteオプションはググってもそれに言及した記事に当たりにくくてなぜ?ってなる。
-exec {} +
方式も複数のファイルに一括コマンド発行してくれるので、これでも速度は出る。
最初この方式は引数上限意識してないのかと思ったけど、manには「xargs(1)とだいたい一緒」という記載はあるし、ARG_MAX制限回避できると言及してる人もいるし、GNUのfindのソース見てみたらxargsという名称組み込んでるみたいだし、なのでやっぱ内部で分割はやってる気がする。
少なくともWSLのUbuntu環境でMAX_ARGS上限に引っかかるファイル数を問題なく処理できるのは確認できた。
xargs方式でも大枠問題ないと思うのだが、各ブログで使えと書かれてる-iオプションは非推奨だったり、そもそもxargs(1)では使うなと書かれたり、特殊文字含むファイル名を意識したり色々注意が必要そう。
ということでxargsはもうちょい調査したほうが良さそうなのだが、一旦限界。rmじゃなくて別の操作したいときは注意+再調査。
参考資料
linux - How to use "xargs" properly when argument list is too long - Stack Overflow
findのdeleteオプションはマイナーなの?
検索してファイルを消す時に高速に行う方法 - Qiita
xargs が思いのほか危険だった - Qiita
chmod や mv すると "bash: Argument list too long" が出るようになった - Qiita
【linux】蓄積されていくログファイルなどを定期的に削除する例 at softelメモ
Stray Penguin - Linux Memo (BASH)