Bashのファイルから最後に変更されたTSを取得する最速の方法は何ですか?

Bashのファイルから最後に変更されたTSを取得する最速の方法は何ですか?

巨大なファイルシステムに何十万ものファイルのリストがある場合、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組み込み関数がzshGNUまたはBSDの前に機能することです(GNUの関数statではありません)。find-printf

zshファイルは再帰的なglobを介して見つけることができるので、他のコマンドを実行せずに全体のプロセスを完了することができますがfind

date -r(また、GNU非標準拡張stat())はlstat()。さまざまなstat実装のいくつかはそれを使用し、いくつかstat()lstat()デフォルトで使用しますが、それらの間を切り替えるように指示するすべての指示があります。

findこれをさらに最適化するには、Cで実装し、追加の保護装置を実装せずにディレクトリナビゲーションを手動で実行できます。最新バージョンのLinuxでは、statx()これを使用してより少ない情報を検索するように指示するのに役立ちます。

locate//存在する場合mlocateplocateキャッシュされたファイルのリストを使用すると、ファイルシステムをクロールする必要がなくなり、プロセスを高速化するのに役立ちます(不十分な情報を提供する危険性があります)。

バージョン4.9以降、GNUはfindstdinを使用してファイルのリストを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/$filepathzshbash$IFS${filepath:-/}

これはread、入力を一度に1バイトずつ読み取る必要があるため、本質的に非効率的です。バラよりシェルループを使用してテキストを処理するのはなぜ悪い習慣と見なされますか?もっと学ぶ。パフォーマンスが問題であれば、適切なプログラミング言語を使用する方が良いでしょう。

ファイル修正時間の検索(および各ファイルに対して別々のユーティリティを実行するための高コストの防止)を基本的にサポートしている私が知っているシェルtcshzshbusyboxksh93ですsh

tcshスクリプトにはあまり役に立ちません。

dateksh93の場合、組み込み関数を使用してビルドする必要がありますlsが、これはまれです。 busyboxの場合、アプレットはそれ自体を再実行せずにアプレットをsh呼び出すことができますが、それでも子プロセスで実行され、プロセスを分岐するのに費用がかかります。statBusybox stat(GNUに似たAPIがありますstat)も1秒未満の精度をサポートしていません。また、NULで区切られたレコードは処理できませんbusybox shksh93

NUL で区切られたファイルパスを含むファイルの場合zsh:list

zmodload zsh/stat || exit
for filepath (${(0)"$(<list)"})
  stat -LF %s.%9. -A timestamp +mtime -- $filepath &&
    something with $filepath and $timestamp

list1行につき(改行なし)ファイルパスを含むファイルパス(0)の場合(f)

ksh93組み込み関数lslist1行に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()渡すのと同じことをすることに注意してください。-Llslstat()


¹これらのdateアプレットは、ビルド時にナノ秒精度をサポートするように設定できますが、デフォルトのビルドでは有効になりません。

おすすめ記事