ディレクトリ再帰しながら、一番新しいファイルを探したい。
「最近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抽出、を書くほうがいい。
プログラミング演習の課題に最適ですな…