web-dev-qa-db-ja.com

findを使用して特定のサブディレクトリを除くすべてのファイルを削除します

サブフォルダーa内のすべてのファイルを除いて、フォルダーb内のしばらくアクセスされなかったすべてのファイルを再帰的に削除したいと思います。

find a \( -name b -Prune \) -o -type f -delete

ただし、次のエラーメッセージが表示されます。

find:-deleteアクションは自動的に-depthをオンにしますが、-depthが有効な場合、-Pruneは何もしません。とにかく続行したい場合は、-depthオプションを明示的に使用してください。

-depthを追加すると、b内のすべてのファイルが含まれますが、これはnot発生する必要があります。

これを機能させる安全な方法を知っている人はいますか?

11
forthrin

TL; DR:最良の方法は、-exec rmの代わりに-deleteを使用することです。

find a \( -name b -Prune \) -o -type f -exec rm {} +

説明:

-delete-Pruneと一緒に使用しようとすると、なぜ文句を言うのですか?

簡単な答え:-delete-depthを意味し、-depth-Pruneを無効にするためです。

長い答えに到達する前に、まず-depthがある場合とない場合のfindの動作を観察します。

$ find foo/
foo/
foo/f1
foo/bar
foo/bar/b2
foo/bar/b1
foo/f2

1つのディレクトリでの順序については保証されません。ただし、ディレクトリがその内容の前に処理されるという保証があります。 foo/の前のfoo/*foo/barの前のfoo/bar/*に注意してください。

これは-depthで元に戻すことができます。

$ find foo/ -depth
foo/f2
foo/bar/b2
foo/bar/b1
foo/bar
foo/f1
foo/

すべてのfoo/*foo/の前に表示されることに注意してください。 foo/barも同様です。

より長い答え:

  • -Pruneは、findがディレクトリに降りないようにします。つまり、-Pruneはディレクトリの内容をスキップします。あなたの場合、-name b -Pruneは、findがbという名前のディレクトリに降りることを防ぎます。
  • -depthは、ディレクトリ自体の前にディレクトリの内容を処理するように検索します。つまり、findがディレクトリエントリbを処理するまでに、その内容はすでに処理されています。したがって、-Pruneは無効であり、-depthは有効です。
  • -delete-depthを意味するため、最初にファイルを削除してから空のディレクトリを削除できます。 -deleteは、空でないディレクトリの削除を拒否します。 -deleteに空でないディレクトリを強制的に削除させたり、-delete-depthを暗示したりしないようにするオプションを追加することは可能だと思います。しかし、それは別の話です。

あなたが望むものを達成する別の方法があります:

find a -not -path "*/b*" -type f -delete

これは覚えやすい場合とそうでない場合があります。

このコマンドは引き続きディレクトリbに移動し、その中のすべてのファイルを-notが拒否するためだけに処理します。ディレクトリbが巨大な場合、これはパフォーマンスの問題になる可能性があります。

-path-nameとは動作が異なります。 -nameは(ファイルまたはディレクトリの)名前とのみ一致し、-pathはパス全体と一致します。たとえば、パス/home/lesmana/foo/barを確認します。名前がbarであるため、-name -barが一致します。文字列-path "*/foo*"がパスにあるため、/fooは一致します。 -pathには、使用する前に理解しておく必要のある複雑さがいくつかあります。詳細については、findのマニュアルページをお読みください。

これは100%絶対確実ではないことに注意してください。 「誤検知」の可能性があります。上記のコマンドの記述方法では、b(正)で始まる名前の親ディレクトリを持つファイルはスキップされます。ただし、ツリー内の位置に関係なく、名前がbで始まるファイルもスキップされます(誤検知)。これは、"*/b*"よりも優れた式を記述することで修正できます。それは読者のための練習として残されています。

プレースホルダーとしてabを使用し、実際の名前はallosaurusbrachiosaurusに似ていると思います。 brachiosaurusの代わりにbを配置すると、誤検知の量が大幅に減少します。

少なくとも誤検知はnot削除されるので、それほど悲劇的ではありません。さらに、最初に-deleteなしでコマンドを実行して(ただし、暗黙の-depthを配置することを忘れないでください)、誤検知をチェックして出力を調べることができます。

find a -not -path "*/b*" -type f -depth
13
lesmana

-deleteの代わりにrmを使用してください:

find a -name b -Prune -o -type f -exec rm -f {} +
3

上記の回答と説明は非常に役に立ちました。

「-exec rm {} +」または「-not -path ... -delete」の回避策を使用しますが、「find ... -delete」よりもはるかに遅くなる可能性があります。「find ... -delete "は、NFSファイルシステムのディープディレクトリで" -exec rm {} + "よりも5倍高速に実行されます。

「パスではない」ソリューションには、除外されたディレクトリ以下のすべてのファイルを調べるという明らかなオーバーヘッドがあります。

「find .. -exec rm {} +」は、システムコールを実行するrmを呼び出します。

fstatat(AT_FDCWD, path...); 
unlinkat(AT_FDCWD, path, 0)

「検索-削除」はシステムコールを実行します。

 fd=open(dir,...);
 fchdir(fd); 
 fstatat(AT_FDCWD, filename,...)
 unlinkat(dirfd, filename,...)

したがって、「-exec rm {} +」rmコマンドは、ファイルごとに2回iノードルックアップへのフルパスを実行しますが、「find -delete」は、現在のディレクトリ内のファイル名の統計とリンク解除を実行します。 1つのディレクトリから多数のファイルを削除する場合、これは大きな利点です。

(泣き言モードオン(申し訳ありません))

-depth、-delete、および-Prune間の相互作用の設計により、「-Pruneディレクトリ内のファイル以外のファイルを削除する」という一般的なアクションを実行する最も効率的な方法が不必要に排除されているようです。

「-typef-delete」の組み合わせは、ディレクトリを削除しようとしないため、-depthなしで実行できるはずです。または、「find」に「ディレクトリを削除しない」という「-deletefile」アクションがある場合は、-depthを暗黙指定する必要はありません。

フルパスのリンクを解除する代わりに、rmがファイル名をソートしてディレクトリを開き、unlinkat(dir_fd、filename)を実行するオプションがある場合、rmコマンドへのxargsまたはfind -exec呼び出しが高速化される可能性があります。 -rオプションを指定してディレクトリを再帰的に実行する場合は、すでにunlinkat(dir_fd、filename)を実行しています。

(泣き言モードオフ)

0
Donald Mears