サブフォルダーa
内のすべてのファイルを除いて、フォルダーb
内のしばらくアクセスされなかったすべてのファイルを再帰的に削除したいと思います。
find a \( -name b -Prune \) -o -type f -delete
ただし、次のエラーメッセージが表示されます。
find:-deleteアクションは自動的に-depthをオンにしますが、-depthが有効な場合、-Pruneは何もしません。とにかく続行したい場合は、-depthオプションを明示的に使用してください。
-depth
を追加すると、b
内のすべてのファイルが含まれますが、これはnot発生する必要があります。
これを機能させる安全な方法を知っている人はいますか?
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*"
よりも優れた式を記述することで修正できます。それは読者のための練習として残されています。
プレースホルダーとしてa
とb
を使用し、実際の名前はallosaurus
とbrachiosaurus
に似ていると思います。 brachiosaurus
の代わりにb
を配置すると、誤検知の量が大幅に減少します。
少なくとも誤検知はnot削除されるので、それほど悲劇的ではありません。さらに、最初に-delete
なしでコマンドを実行して(ただし、暗黙の-depth
を配置することを忘れないでください)、誤検知をチェックして出力を調べることができます。
find a -not -path "*/b*" -type f -depth
-delete
の代わりにrm
を使用してください:
find a -name b -Prune -o -type f -exec rm -f {} +
上記の回答と説明は非常に役に立ちました。
「-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)を実行しています。
(泣き言モードオフ)