1. 概要

Linuxコマンドラインでは、 find コマンドを使用して、ファイルまたはディレクトリのリストを取得できます。 通常、 findおよびtarファイルなど、見つかったファイルに対していくつかの操作を実行する必要があります。

このチュートリアルでは、見つけたファイルまたはディレクトリを削除する方法を見ていきます。

2. 問題の紹介

findコマンドで見つかったファイルとディレクトリを削除する方法はいくつかあります。 それは難しい問題ではありません。 おそらく、私たちはすでにいくつかの解決策を考えています。

ただし、一部のソリューションは、正しく使用しないと危険な場合があります。 さらに、一部のソリューションはパフォーマンスの点でうまく機能しない場合があります。

このチュートリアルの残りの部分では、 find コマンドを使用する際の一般的な落とし穴を詳しく見て、なぜそれが危険なのかを説明します。

さらに、パフォーマンスについても説明します。

まず、例としてディレクトリ構造を作成しましょう。

$ tree -a test
test
├── kotlin
│   ├── ktApp1
│   │   └── .git
│   │       └── whatever.txt
│   └── ktApp2
│       └── .git
│           └── whatever.txt
└── python
    ├── pyApp1
    │   └── .git
    │       └── whatever.txt
    └── pyApp2
        └── .git
            └── whatever.txt

10 directories, 4 files

上記のtreeの出力が示すように、いくつかのサブディレクトリとファイルを含むtestディレクトリを作成しました。

testディレクトリで2つの削除を試みます。

  • ファイルの削除:すべてのwhatever.txtファイルを削除します
  • ディレクトリの削除:すべての gitディレクトリとその下のファイルを削除します

find コマンドを見て、ターゲットディレクトリとファイルを見つけましょう。

まず、すべてのwhatever.txtファイルを見つけましょう。

$ find test -name 'whatever.txt'
test/python/pyApp2/.git/whatever.txt
test/python/pyApp1/.git/whatever.txt
test/kotlin/ktApp2/.git/whatever.txt
test/kotlin/ktApp1/.git/whatever.txt

同様に、.gitディレクトリも見つけることができます。

$ find test -type d -name '.git'
test/python/pyApp2/.git
test/python/pyApp1/.git
test/kotlin/ktApp2/.git
test/kotlin/ktApp1/.git

このチュートリアルでは、ターゲットファイルとディレクトリを削除するための3つのアプローチを紹介します。

  • find コマンドの-deleteアクションを使用する
  • find-execを使用する
  • findを使用する| xargs rm

これまで、findコマンドを使用して削除するファイルまたはディレクトリを見つける方法を見てきました。 また、Linuxコマンドをパイプに接続し、さまざまなコマンドで問題を協調的に解決できることもわかっています。

私たちの多くは、この問題を解決するための最も簡単なアプローチは、findの結果をrmコマンドにパイプすることだと思うかもしれません。 少し驚くべきことは、それが上記の箇条書きリストにないことです。

したがって、問題の実際の解決策を検討する前に、findの結果をrm。にパイプできない理由を理解しましょう。

3. なぜ「検索…| rm」動作しませんか?

この質問に答える前に、パイプが何をするのかを理解する必要があります。 まず、例を見てみましょう。

$ ls -1 / | grep '^m'
media/
mnt/

上記の簡単な例では、lsコマンドの結果をgrepにパイプ処理し、名前が「m」で始まるルートディレクトリを見つけます。

簡単に言えば、ここでは、パイプは ls の標準出力(Stdout)を grep コマンドの標準入力(Stdin)に変換します。

grepコマンドがStdinからの読み取りを受け入れるため、このコマンドは機能します。 Stdoutを、Stdinからの読み取りをサポートする他のコマンドにパイプできます。次に例を示します。

$ ls -1 / | grep '^m' | sed 's/^m/OK_m/'
OK_media/
OK_mnt/

この種の「コマンドチェーン」は、現実の世界ではかなり頻繁に見られます。

ただし、すべてのLinuxコマンドがStdinからの読み取りをサポートしているわけではありません。 典型的な例は、ファイル処理を行うコマンドです。 たとえば、cp、mv、およびrmです。 これらのコマンドはStdinを無視します

たとえば、コマンド「 rm file 」を実行すると、rmはファイルを示すコマンドライン引数fileを受け入れます。 Stdinはまったく読み取られません。

$ echo "file" | rm
rm: missing operand

したがって、「 探す …。 | rm 」も機能しません。

ただし、あるコマンドのStdoutを別のコマンドの引数に変換したい場合があります。 そこで、xargsが役に立ちます。 後のセクションで実際に動作することを確認します。

それでは、「検索と削除」の問題の解決策を見ていきましょう。

4. findコマンドと-deleteアクションの使用

findコマンドは、ファイルを削除するための-deleteアクションを提供します。次に、このアクションを使用してターゲットファイルとディレクトリを削除しましょう。

4.1. ターゲットファイルとディレクトリの削除

[X90X]-deleteオプションをfindコマンドに追加することで、すべてのwhatever.txtファイルを削除できます。

$ find test -name 'whatever.txt' -delete

$ tree -a test
test
├── kotlin
│   ├── ktApp1
│   │   └── .git
│   └── ktApp2
│       └── .git
└── python
    ├── pyApp1
    │   └── .git
    └── pyApp2
        └── .git

10 directories, 0 files

良い、それは動作します。 すべてのwhatever.txtファイルが削除されました。

次に、 test ディレクトリを復元し、.gitディレクトリを再帰的に削除してみましょう。

$ find test -type d -name '.git' -delete
find: cannot delete ‘test/python/pyApp2/.git’: Directory not empty
find: cannot delete ‘test/python/pyApp1/.git’: Directory not empty
find: cannot delete ‘test/kotlin/ktApp2/.git’: Directory not empty
find: cannot delete ‘test/kotlin/ktApp1/.git’: Directory not empty

おっと、今回はエラーメッセージが表示されました。 これは、-deleteアクションが空でないディレクトリを再帰的に削除できないためです。 つまり、削除できるのはファイルと空のディレクトリのみです。

4.2. の危険な落とし穴-deleteの使用法

次に、興味深いテストを行いましょう。 Linuxコマンドのオプションの順序は通常重要ではないことを私たちは知っています。

たとえば、次の2つの ls コマンドは、オプションの順序が異なっていても同じです。

ls -F -a -l --color
ls -l -a --color -F

次に、 -delete オプションを最初の位置に移動して、最後の find コマンドのオプションを並べ替えて、何が起こるかを確認しましょう。

$ find test -delete -type d -name '.git'
$ ls test
ls: cannot access 'test': No such file or directory

今回はエラーメッセージはありません。 コマンドが正常に実行されたことを意味します。

しかし、結果を確認すると、 test ディレクトリが完全に削除されていることがわかりました! それが起こった理由を理解しましょう。

findコマンドをもう一度見てみましょう。 -delete -type d 、および-name’.git’の3つのオプションを呼び出すことができます。 ただし、findはそれらを3つの式としても扱うことを忘れてはなりません。

find コマンドの式が評価され、ブール値が返され、-deleteアクションは常にtrueを返します。 

-deleteアクションが最初の位置にある場合、評価中に、指定されたディレクトリとその中のすべてを削除します、この例ではtestディレクトリです。

しかし、待ってください。 -delete アクションでは、空でないディレクトリは削除されないことを学びました。 テストのすべてが削除されたのはなぜですか?

これは、-deleteアクションが-depthオプションを意味するためです。

-depth オプションは、 find コマンドに、ディレクトリ自体の前に各ディレクトリの内容を検索するように要求します。 したがって、最初のオプションとして -delete を指定すると、各ディレクトリツリーの最下部から削除が開始されます。 まず、ディレクトリの下にあるすべてのファイルを削除し、次にすべてが削除されるまで空のディレクトリ自体を削除します。

find コマンドを使用するときは、最初の位置に-deleteアクションを配置しないように注意する必要があります。 そうすると、予期せずファイルが削除される可能性があります。

5. find-execを使用する

findコマンドを-execアクションとともに使用すると、その結果に対して外部コマンドを実行できます。 次に、 rm コマンドを実行して、このアプローチでターゲットファイルとディレクトリを削除しましょう。

$ find test -name 'whatever.txt' -exec rm {} \;
$ tree -a test
test
├── kotlin
│   ├── ktApp1
│   │   └── .git
│   └── ktApp2
│       └── .git
└── python
    ├── pyApp1
    │   └── .git
    └── pyApp2
        └── .git

10 directories, 0 files

了解しました。すべてのwhatever.txtファイルが削除されました。 外部コマンドで-execを使用すると、見つかった各ファイルが「{}」プレースホルダーに入力されます。

同様に、-rオプションをrmコマンドに追加すると、すべての.gitディレクトリを削除できます。 test ディレクトリを復元して、試してみましょう。

$ find test -depth -type d -name '.git' -exec rm -r '{}' \;
$ tree -a test
test
├── kotlin
│   ├── ktApp1
│   └── ktApp2
└── python
    ├── pyApp1
    └── pyApp2

6 directories, 0 files

出力が示すように、すべての.gitディレクトリが正常に削除されました。

6. 検索の使用| xargsrm組み合わせ

これで、 find の– execアクションを使用してrmコマンドを実行できることがわかりました。 または、findコマンドの結果をxargsにパイプし、xargsrmコマンドを呼び出してこれらのファイルを削除することもできます。

次に、このアプローチを使用してすべてのwhatever.txtファイルを削除する方法を見てみましょう。

$ find test -name 'whatever.txt' | xargs rm
$ tree -a test
test
├── kotlin
│   ├── ktApp1
│   │   └── .git
│   └── ktApp2
│       └── .git
└── python
    ├── pyApp1
    │   └── .git
    └── pyApp2
        └── .git

10 directories, 0 files

同様に、すべての.gitディレクトリを同じ方法で削除することもできます。 test ディレクトリを復元して、テストしてみましょう。

$ find test -type d -name '.git' | xargs rm -r
$ tree -a test
test
├── kotlin
│   ├── ktApp1
│   └── ktApp2
└── python
    ├── pyApp1
    └── pyApp2

6 directories, 0 files

上記の出力が示すように、すべての.gitディレクトリが削除されました。

find -exec rm で問題を解決できる場合、同じことを行うために追加の xargs プロセスを導入する必要があるのはなぜですか?

答えを学ぶために、彼らのパフォーマンスについて話し合いましょう。

7. find-execおよびfind|のパフォーマンスのベンチマーク xargs

まず、 find -execrmアプローチがどのように機能するかを説明しましょう。 このアプローチを使用すると、findコマンドが検出したファイルごとにrmプロセスが実行されます。 つまり、 find コマンドが100万個のファイルを検出した場合、rmコマンドを100万回実行します。

一方、find|を実行すると xargs rm xargs は、見つかったファイルをバンドルにビルドし、コマンドを可能な限り実行します

したがって、 findコマンドが多数のファイルまたはディレクトリを返す場合は、find | xargs COMMANDは、find -execCOMMANDapproachよりもはるかに高速です。

次に、各アプローチで同じパフォーマンステストを実行し、それらのパフォーマンスのベンチマークを行います。

各コマンドを使用して3000ファイルを削除し、timeコマンドを使用してそれらの実行時間を測定します。

まず、 find -execrmアプローチでテストしてみましょう。

$ touch {1..3000}.txt
$ ls -l *.txt | wc -l
3000
$ time find . -name '*.txt' -exec rm '{}' \;
real	0m6.072s
user	0m3.130s
sys	0m2.932s

このマシンでは、すべてのファイルを削除するのに約6秒かかりました。

次に、xargsの番です。 同じテストをより速く実行できるかどうかを見てみましょう。

$ touch {1..3000}.txt
$ ls -l *.txt | wc -l
3000
$ time find . -name '*.txt' | xargs rm
real	0m0.053s
user	0m0.029s
sys	0m0.029s

今回は、ファイルを削除するのに0.05秒しかかかりませんでした。 find -exec rm アプローチと比較すると、このテストで xargs を使用すると、120倍速くなります。 わお!

したがって、 findが多数のファイルを返す場合は、結果をxargsコマンドにパイプすることを検討する必要があります。

8. 結論

この記事では、findコマンドで見つかったファイルまたはディレクトリを削除する3つの異なる方法を学びました。 また、findの出力rmにパイピングできない理由も理解しました。

さらに、例を通してfind-deleteアクションの使用法の危険な落とし穴について説明しました。

最後に、 find -execrmfind|の2つのアプローチのパフォーマンスも分析しました。 xargsrm