ディレクトリ再帰しながら、一番新しいファイルを探したい。
「最近3日以内に更新されたファイル」なら find
(1) で
簡単に探せますが、
「一番新しいファイル」をどうやって探し当てるか、
というコマンドラインを作ろうとした際の思考過程。
# ファイル数が1024個以下程度の時 % ls -t `find . -type f` | head -1 # ファイル数が10万個程度までの時 % find . -type f | xargs -n 100 sh -c 'ls -t $@ | head -1' | xargs ls -t | head -1 # ファイル数がとても多いとき % find $1 -type f -print0 | xargs -0 -n100 stat -f "%m %N" | awk 't<$1{t=$1;n=$0} END{print n}' | sed -e 's/^[^ ]* //'
$Id: newest-file.html,v 1.7 2016-10-27 09:36:11+09 kabe Exp $
ls -t
だろう% ls -t newest-file.html.vim xon.html C1400.html newest-file.html sjis.ASCII.article.html hearts.html.ko RCS joy.news anon.html ...んーと。最新のだけ欲しい。
% ls -t | head -1 newest-file.html.vimでも、これだとディレクトリ再帰しないな。 再帰させたいとすると、…
ls -tR
だとディレクトリごとのソートにしかならないから、
んーと、find
でいったんファイルだけ吐かせて、
% find . -type f ./tips.html ./fonts.html ./garbled.html ...
ls -t
でソートしよう。
% ls -t `find . -type f` ./newest-file.html.vim ./2ch-portscan-ref.html ./newest-file.html ./RCS/xon.html,v ...頭だけ欲しいから、
head
追加。
% ls -t `find . -type f` | head -1 ./newest-file.html.vim
(argc制限) ls -t `find . -type f`
は、
ls
の引数として直接、全部のファイルを詰め込んでいるので、
ファイル数が大きすぎると使えない。
だいたい1024個あたりで限界がくる。
% ls -t `find /opt/f-secure -type f` | head -1 find: /opt/f-secure/fsigk/auth/global/cert: Permission denied /bin/ls: Argument list too long.
(処理コスト) ファイル数をNとすると、
find
は O(N)
ls -t
は ソートが支配的として、O(NlogN)
最新(mtime値が一番大きいもの)だけ拾い出したいなら O(N)でできるはずだが、そういうプログラムを書く必要がある。 Nの値が大きい (100万ファイルとか)だと考える必要がある。
(空白の処理) これだとファイル名に空白が入っているとうまく動かない、
かもしれない。MacOS X とか。
シェルのバッククオート引数渡しにせず、
素直にxargs
使ったほうがいいかも。
% ls -t `find /opt/f-secure -type f` | head -1 find: /opt/f-secure/fsigk/auth/global/cert: Permission denied /bin/ls: Argument list too long.こういう場合はもうちょっと真面目に考えよう。
えーと、100個くらいずつにちぎって、後でマージソートしよう。
% find /opt/f-secure -type f | _んーと。 パイプに流れているファイル名の羅列を 100個ずつに切るにはどうすればいいかな? そういうコマンドは無いけど、わざわざ
perl
なんか使いたくない。
xargs -n 100
を使うか。
% find /opt/f-secure -type f | xargs -n 100 _で、これを
ls -t
でソートする…
% find /opt/f-secure -type f | xargs -n 100 ls -t _これだと全部出てきてしまうな。100個ずつでソートしたものの頭だけ欲しいんだ。 どうやって
head
を噛ませればいいかな?
…こうか
% find /opt/f-secure -type f | xargs -n 100 sh -c 'ls -t "$@" | head -1' _これで、100個ずつのカタマリの中で最新のファイル名が順次出てくる。 この一覧が1024個程度以下であれば
ls -t
に食わせられる。
% find /opt/f-secure -type f | xargs -n 100 sh -c 'ls -t "$@" | head -1' | _
こんなんか?もう一回 xargs
を使う。
% find /opt/f-secure -type f | xargs -n 100 sh -c 'ls -t "$@" | head -1' | xargs ls -t find: /opt/f-secure/fsigk/auth/global/cert: Permission denied /opt/f-secure/fsigk/log/admin/catalina.out /opt/f-secure/fsigk/log/http/access.log /opt/f-secure/fsigk/log/pop/access.log ...で、頭だけ採ればいいわけだね?
% find /opt/f-secure -type f | xargs -n 100 sh -c 'ls -t "$@" | head -1' | xargs ls -t | head -1 find: /opt/f-secure/fsigk/auth/global/cert: Permission denied /opt/f-secure/fsigk/log/admin/catalina.out
(空白の処理) 問題あり。
find -print0
と xargs -0
のコンビが本来は必要。
2段目以降のxargsでは -0
が使えない。
(GNU xargs ならxargs --delimiter
で逃げられるが)
ふっるいsh
だと "$@"
がうまく展開できないやつも
あるかもしれない。
この辺の移植性については autoconf のマニュアルが
地味に参考になる。
(処理コスト) ファイル数をNとすると、
find
は O(N)。ただし、ディスク負荷は意外に大きい。
ファイルシステムの構造とか考え始めるとO(N)では足らないかも?
xargs -n 100 sh -c 'ls -t $@ | head -1'
は、O(NlogN) を ∝N回
繰り返し、だから O(N2logN) かもしれないけど、
100個のカタマリづつでソートしてるだけだから、
全体としてはO(NlogN)だろう。
ただ、いちいち /bin/sh
を起動してるから、あんまり速くない。
ls -t
は O(NlogN)
Nの値が100万ファイルとかなら、1000個くらいずつに区切れば一応処理できるかもしれない。
もっと多そうなら、ls -t
を2段ロケットにして…
いやいやいや。
そもそも find
で時間・負荷がかかりすぎて破綻すると思う。
ディレクトリの atime 更新だけでディスクI/Oがお腹いっぱいになってしまう。
こういう全数検査ではなくて、「ディレクトリのmtimeを見て、更新があったら探しに行く」
みたいなことをしないと。
1回だけならこれでもいいけど、定期起動するバッチファイルに埋めるなら ちゃんと考えて設計・実装しないと駄目ですね。 ちゃんと作れば O(N) になりそう。
ls -t
では真面目に全部ソートしてしまうので、
100万ファイルとかだと無駄な処理が多すぎる。O(N)で作りたい。
find $1 -type f -print0 | # ファイル名を全部吐かせる xargs -0 -n100 stat -f "%m %N" | # mtimeとファイル名を吐かせる。GNU(Linux) stat(1)なら stat -c "%Y %n" awk 't<$1{t=$1;n=$0} END{print n}' | # O(N)で、mtime が一番大きい行を抽出 sed -e 's/^[^ ]* //' # ファイル名だけ抽出。ファイル名にスペースがあってもok
xargsはスペースでも引数が区切られるので、スペース入りファイル名を
正しく扱うには find -print0
と xargs -0
のコンビが必要。
これでも stat(2) がファイルあたり2回実行されるので、無駄あり。
本気で作るならシェルではなく
Cやperlで再帰下降+最大mtime抽出、を書くほうがいい。
プログラミング演習の課題に最適ですな…