巨大なファイルシステムに何十万ものファイルのリストがある場合、bashからすべてのファイルの最後の修正時間を取得する最速の方法は何ですか?
速度を向上させるために並べ替える方法がないと仮定すると、この質問の中心は実際には次のとおりです。 bashでファイルの最後の修正時間を取得する最速の方法は何ですか?stat
最も一般的なアプローチのようですがfind
(with -printf "%T+"
)とdate -r
。
(ファイルシステムによって異なりますか?)
ベストアンサー1
GNUがある場合find
(GNUサポート-printf
)。
find /filesystem/mount/point -xdev -printf '%T@\t%p\0' > timestamps
最速です。find
ディレクトリツリーを巡回し、システムがlstat()
独自の呼び出しでタイムスタンプを取得するように高度に最適化されています。また、lstat()
パスが見つかったディレクトリに相対的なパスを呼び出します。つまり、lstat()
フルパスを呼び出すよりもカーネルが実行する作業が少なくなります。
タイムスタンプを10進時代の時間で印刷したら、%T@
数字(秒とナノ秒)を2進から10進に変換するだけです。これは%T+
、ユーザーのタイムゾーンでカレンダー時間を計算するよりもはるかに少ない作業です。
コマンドにはさまざまで互換性のない実装がたくさんありますが、それらのどれもファイルを見つけることができません。パスを引数として指定したファイルからメタデータ情報を取得するには、//または同等の操作を実行するstat
だけなので、検索するには別のものが必要です。stat()
ファイルをダウンロードし、フルパスを 。lstat()
statx()
statfs()
stat
ほとんどのシステムでは、コマンドは限られた数の引数しか受け入れることができないため、これはユーティリティをstat
複数回呼び出す必要があり、毎回独自のプロセスで引数をロード、初期化、および処理する必要があるたびに待機する必要があることを意味します。
1つの例外は、stat
組み込み関数がzsh
GNUまたはBSDの前に機能することです(GNUの関数stat
ではありません)。find
-printf
zsh
ファイルは再帰的なglobを介して見つけることができるので、他のコマンドを実行せずに全体のプロセスを完了することができますがfind
。
date -r
(また、GNU非標準拡張stat()
)はlstat()
。さまざまなstat
実装のいくつかはそれを使用し、いくつかstat()
はlstat()
デフォルトで使用しますが、それらの間を切り替えるように指示するすべての指示があります。
find
これをさらに最適化するには、Cで実装し、追加の保護装置を実装せずにディレクトリナビゲーションを手動で実行できます。最新バージョンのLinuxでは、statx()
これを使用してより少ない情報を検索するように指示するのに役立ちます。
locate
//存在する場合mlocate
、plocate
キャッシュされたファイルのリストを使用すると、ファイルシステムをクロールする必要がなくなり、プロセスを高速化するのに役立ちます(不十分な情報を提供する危険性があります)。
バージョン4.9以降、GNUはfind
stdinを使用してファイルのリストをprocessに渡すことができるので、-files0-from -
次のことができます。
LC_ALL=C locate -0 '/filesystem/mount/point/*' |
find -files0-from - -prune -printf '%T@\t%p\0' > timestamps
| xargs -r0 stat --printf '%.9Y\t%n\0' --
これは、複数の呼び出しを実行する同様のものを使用するよりもstat
効率的です(ここではGNUがあり、入力ファイルパスがないと仮定します)。-
stat
ファイルにNULで区切られたレコードとして保存されているファイルパスのリストがある場合は、同じアプローチを使用できます。他の形式の場合は、最初に変換する必要があります。たとえば、1行に1つのパスを含むテキストファイルに対してこれを行うことができます。つまり、改行文字を含むファイルパスを保存することはできませんtr '\n' '\0' < list.txt | find...
。
私のテストでは、find
ファイルを直接見つけるよりもまだ効率が悪いです。おそらくフルパスをfind
呼び出すことになるので、カーネルは各ファイルに対してフルルックアップを実行する必要があります。lstat()
また、これより長いファイルパスは処理できませんPATH_MAX
(通常、Linuxでは約4KiB、出力を参照)。getconf PATH_MAX /mount/point
とにかくパフォーマンスを向上させるために最後にやりたいことは、シェルループと同様に、各ファイルに対して外部ユーティリティ(GNUdate
やGNUなど)を実行することです。stat
何らかの理由でシェルでファイルとそのタイムスタンプを繰り返す必要がある場合(たとえば、組み込み関数がbash
ない場合)、次のことができます。stat
while IFS=/ read -u3 -rd '' timestamp filepath; do
something with "$timestamp" and "$filepath"
done 3< <(find /filesystem/mount/point -xdev -printf '%T@/%p\0')
/
区切り文字は、ファイルパスの末尾に表示されないことが保証される唯一の文字であるために使用します。 1つの例外はに渡すディレクトリですfind
。たとえば、出力では、find / -xdev -printf '%T@/%p\0'
最初のレコード(および最初のレコードのみ)が終了します/
。これにはが含まれ、inの代わりに空の文字列が格納さ<timestamp>//
れます。代わりに、(実際には区切り文字ではなく内部フィールド区切り文字として扱われる場合)、ファイルパスを参照するときにこの問題を解決できます。read
/
$filepath
zsh
bash
$IFS
${filepath:-/}
これはread
、入力を一度に1バイトずつ読み取る必要があるため、本質的に非効率的です。バラよりシェルループを使用してテキストを処理するのはなぜ悪い習慣と見なされますか?もっと学ぶ。パフォーマンスが問題であれば、適切なプログラミング言語を使用する方が良いでしょう。
ファイル修正時間の検索(および各ファイルに対して別々のユーティリティを実行するための高コストの防止)を基本的にサポートしている私が知っているシェルtcsh
とzsh
busyboxksh93
ですsh
。
tcsh
スクリプトにはあまり役に立ちません。
date
ksh93の場合、組み込み関数を使用してビルドする必要がありますls
が、これはまれです。 busyboxの場合、アプレットはそれ自体を再実行せずにアプレットをsh
呼び出すことができますが、それでも子プロセスで実行され、プロセスを分岐するのに費用がかかります。stat
Busybox stat
(GNUに似たAPIがありますstat
)も1秒未満の精度をサポートしていません。また、NULで区切られたレコードは処理できませんbusybox sh
。ksh93
NUL で区切られたファイルパスを含むファイルの場合zsh
:list
zmodload zsh/stat || exit
for filepath (${(0)"$(<list)"})
stat -LF %s.%9. -A timestamp +mtime -- $filepath &&
something with $filepath and $timestamp
list
1行につき(改行なし)ファイルパスを含むファイルパス(0)
の場合(f)
。
ksh93
組み込み関数ls
とlist
1行に1つのファイルパスを使用してください。
builtin ls || exit
while IFS= read -ru3 filepath; do
timestamp=${ ls -dZ '%(mtime:%s.%N)s' -- "$filepath"; } &&
something with "$filepath" and "$timestamp"
done 3< list
builtin date; date -f %s.%N -m -- "$filepath"
ここでも使用できますが。代わりににstat()
渡すのと同じことをすることに注意してください。-L
ls
lstat()
¹これらのdate
アプレットは、ビルド時にナノ秒精度をサポートするように設定できますが、デフォルトのビルドでは有効になりません。