「検索」に名前が変更されたファイルを「検索」しますか?なぜできないの?

「検索」に名前が変更されたファイルを「検索」しますか?なぜできないの?

答えながら古い質問驚くべきことにfind、以下の例では、ファイルを複数回処理することが可能なようです。

find dir -type f -name '*.txt' \
    -exec sh -c 'mv "$1" "${1%.txt}_hello.txt"' sh {} ';'

またはより効率的

find dir -type f -name '*.txt' \
    -exec sh -c 'for n; do mv "$n" "${n%.txt}_hello.txt"; done' sh {} +

このコマンドはファイルを探し、そのファイル.txt名のサフィックスを 。.txt_hello.txt

これにより、ディレクトリは*.txt名前がパターンと一致する新しいファイルの蓄積を開始します_hello.txt

質問:実際に処理されないのはなぜですかfind?私の経験ではそうではなく、私たちもそうしたくないからです。そうすると無限ループが発生するからです。ところでmv代替の場合も同様です。cp

これPOSIX規格によると(私の強調)

検索中のディレクトリ階層からファイルが削除または追加された場合find検索にファイルを含めるかどうかは指定されていません。

新しいファイルを含めるかどうかが指定されていないため、おそらくより安全なアプローチは次のとおりです。

find dir -type d -exec sh -c '
    for n in "$1"/*.txt; do
        test -f "$n" && mv "$n" "${n%.txt}_hello.txt"
    done' sh {} ';'

ここではファイルではなくディレクトリを探しており、スクリプトfor内のループはsh最初の反復の前にその範囲を一度評価するため、同じ潜在的な問題は発生しません。

GNUfindマニュアルはこれを明示的に明らかにせず、findOpenBSDマニュアルも同様です。

答え1

findディレクトリを巡回しながら生成されたファイルを見つけることができますか?

つまり、そうです。しかし、実装によって異なります。すでに処理されているファイルは無視されるように条件を作成することをお勧めします。

言及したように、POSIXはどちらも保証しません。readdir()基本的なシステムコールは保証されません。:

最後の呼び出しopendir()時以降にファイルが削除されるかディレクトリに追加された場合、後続の呼び出しがrewinddir()そのファイルのエントリを返すかどうかreaddir()は指定されません。


find私のDebian(GNU検索、Debianパッケージバージョン)4.6.0+git+20161106-2でテストしました。straceタスクを実行する前にディレクトリ全体を読み取ることを示します。

ソースコードをもう少し調べると、GNU findはgnulibの一部を使ってディレクトリを読み取っているようです。gnulib/lib/fts.cgl/lib/fts.c圧縮findパッケージ):

/* If possible (see max_entries, below), read no more than this many directory
   entries at a time.  Without this limit (i.e., when using non-NULL
   fts_compar), processing a directory with 4,000,000 entries requires ~1GiB
   of memory, and handling 64M entries would require 16GiB of memory.  */
#ifndef FTS_MAX_READDIR_ENTRIES
# define FTS_MAX_READDIR_ENTRIES 100000
#endif

制限を100に変更して実行しました。

mkdir test; cd test; touch {0000..2999}.foo
find . -type f -exec sh -c 'mv "$1" "${1%.foo}.barbarbarbarbarbarbarbar"' sh {} \; -print

名前が5回変更されたこのファイルのような面白い結果が出ます。

1046. Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba Baba

明らかに、GNU findのデフォルトのビルドでこの効果を引き起こすには、非常に大きなディレクトリ(100,000を超えるエントリ)が必要ですが、キャッシュなしの単純なreaddir + processループはより脆弱です。

理論的には、この単純な実装は、オペレーティングシステムが常にファイルが返される順序で最後に名前が変更されたファイルを追加すると、無限readdir()ループに陥ることがあります。

Linuxでは、readdir()Cライブラリはgetdents()すでに複数のディレクトリエントリを一度に返すシステムコールによって実装されています。これは、後で呼び出しがreaddir()削除されたファイルを返すことができますが、非常に小さいディレクトリの場合は、起動状態のスナップショットを効果的に取得できることを意味します。他のシステムはわかりませんね。

上記のテストでは、ファイル名がその場所で上書きされるのを防ぐために、意図的に長いファイル名に名前を変更しました。とにかく、同じ長さの名前の変更に対する同じテストが、二重および三重の名前変更で行われた。もちろん、これが重要かどうかや方法は、ファイルシステムの内部構造によって異なります。

findこれらすべてを念頭に置いて表現することで、全体的な問題を避けることが賢明かもしれません。いいえ処理されたファイルと一致します。つまり、-name "*.foo"私の例や! -name "*_hello.txt"質問のコマンドに追加されました。

関連情報