web-dev-qa-db-ja.com

変数にファイル名パターンがあるxargs rmを見つける

このようなコマンド:

find /directory -type f -name "*.txt" -print | xargs rm

ディレクトリとサブディレクトリ内のすべての.txtファイルを削除します。これで問題ありません。しかし、ファイル拡張子の変数または配列を作成し、次にfindを配置すると、たとえば、

TXT=(*.txt)
for ii in ${TXT[@]}; do
  find /directory -type f -name $TXT -print | xargs rm
done

このコマンドは、サブディレクトリ内の.txtファイルを削除しません。どうして?この2番目のコードを変更してサブディレクトリ内のファイルを削除する方法は?

PS:複数のファイル拡張子があるため、配列を使用しました。

3
Kris

配列の割り当て

TXT=(*.txt)

*.txtパターンを、そのパターンに一致する現在のディレクトリ内のファイル名のリストに展開します。シェルは、割り当て時にこれを行います。これはあなたが望むものではありません。次のように、findにリテラル文字列*.txtを指定します。

pattern='*.txt'
find /directory -type f -name "$pattern" -exec rm {} +

ここでは、xargs rmも削除しており、代わりにrmfindから直接実行しています。 findの最新の実装では、-deleteの代わりに非標準の-exec rm {} +を使用できます。

pattern='*.txt'
find /directory -type f -name "$pattern" -delete

ここでは1つのパターンのみを扱っているため、ここではループは必要ありません。また、findの呼び出しでの"$pattern"の引用が重要であることに注意してください。そうしないと、findが開始する前に、パターンが現在のディレクトリで一致するすべてのファイル名に置き換えられます。

いくつかのパターンでは、次のようなループを実行できます。

patterns=( '*.txt' '*.tmp' )
for pattern in "${patterns[@]}"; do
    find /directory -type f -name "$pattern" -delete
done

シェルがパターンをファイル名のグロビングパターンとして使用しないようにするため、配列割り当てでの引用は不可欠です。同じ理由から、"${patterns[@]}""$pattern"の引用も同様に重要です。

別のアプローチは、複数のパターンがある場合でも、findを1回だけ呼び出すことです。 /directoryが大規模なディレクトリ階層である場合、これはかなり高速になります。次のコードは、使用するfind-nameテストの配列を作成することにより、これを実行します。

patterns=( '*.txt' '*.tmp' )

name_tests=( )
for pattern in "${patterns[@]}"; do
    name_tests+=( -o -name "$pattern" )
done

# "${name_tests[@]:1}" removes the initial "-o", which shouldn't be there.
name_tests=( '(' "${name_tests[@]:1}" ')' )

find /directory -type f "${name_tests[@]}" -delete

上記のスクリプトでは、最後に実行される実際のコマンドは次のようになります。

find /directory -type f '(' -name '*.txt' -o -name '*.tmp' ')' -delete

...ファイル名のサフィックス.txtor.tmpを含むすべての通常のファイルを、ディレクトリ/directory内またはその下に削除します。

7
Kusalananda