次のコマンドを実行すると:
#!/bin/bash
while IFS= read -r -d '' file; do
files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"
これと同じ結果は得られません:
#!/bin/bash
find_args="-type f '*.c' -print0"
while IFS= read -r -d '' file; do
files+=$file
done < <(find $find_args)
echo "${files[@]}"
2番目のシナリオを最初のシナリオと同等に修正するにはどうすればよいですか?
私の理解では、二重引用符には一重引用符があるため、一重引用符はエスケープされ、次のような悪い展開が生成されます。
find -type f -name ''\''*.c'\'' -print0
(タイプミスがあることに注意してください。2番目の例では-name
フラグを省略しました。)
1つのアプローチは、配列に引数を配置し、配列をfind
..に適切に渡すことです。
#!/bin/bash
find_args=(-type f -name '*.c' -print0)
while IFS= read -r -d '' file; do
files+=$file
done < <(find "${find_args[@]}")
echo "${files[@]}"
形式${foo[@]}
は、配列のすべての要素に展開され、それぞれが(単一の文字列に展開されるのではなく)個々の単語に展開されます。これは、元のスクリプトに近い意図です。
BLayerの答え は正しいですが、ここで実際に起こっていることを分解するには(欠落している-name
プライマリのタイプミスを無視して):
#!/bin/bash
while IFS= read -r -d '' file; do
files+=$file
done < <(find -type f -name '*.c' -print0)
echo "${files[@]}"
プロセス置換(<(...)
)によって開始されたシェルでは、次のコマンドがbashによって解析されます。
find -type f -name '*.c' -print0
Glob *.c
が引用されているため、bashはそれを展開しませんnot。ただし、一重引用符は削除されます。したがって、find
プロセスが開始されると、引数リストとして表示されるのは次のとおりです。
-type
f
-name
*.c
-print0
これらの引数は、スペースや改行ではなく、nullバイトで区切られていることに注意してください。これは、シェルレベルではなく、経営幹部レベルです。これは、Cでexecve()
を使用してプログラムを実行する方法と関係があります。
対照的に、次のスニペットでは次のようになります。
#!/bin/bash
find_args="-type f -name '*.c' -print0"
while IFS= read -r -d '' file; do
files+=$file
done < <(find $find_args)
echo "${files[@]}"
変数find_args
の値は次のように設定されます。
-type f -name '*.c' -print0
(二重引用符は値の一部ではありませんが、一重引用符はあります。)
コマンドfind $find_args
が実行されると、man bash
に従って、トークン$find_args
はパラメーター拡張の後にワード分割が続きます。続いてパス名展開(別名glob展開)。
パラメータ展開後、-type f -name '*.c' -print0
があります。これはafter引用符の削除であることに注意してください。したがって、一重引用符は削除されません。
単語分割後、次の単語が別々の単語になります。
-type
f
-name
'*.c'
-print0
次にパス名の展開が行われます。もちろん、ファイル名に通常は一重引用符を入れないため、'*.c'
は何にも一致しない可能性が高いため、結果は可能性が高い'*.c'
はリテラルパターンとしてfind
に渡されるため、-name
プライマリはすべてのファイルで失敗します。 (名前が一重引用符で始まり、3文字の.c'
で終わるファイルがある場合にのみ成功します)
編集:実際には、そのようなファイルがある場合、glob '*.c'
はそのファイルと他のそのようなファイルに一致するように展開され、次に展開[実際のファイル名]はパターンとしてfind
に渡されます。したがって、-print0
プライマリに到達するかどうかは、(a)のみが存在するかどうかによって異なります。 oneそのようなファイル名、および(b)globとして解釈されるそのファイル名がそれ自体と一致するかどうか。
例:
touch "'something.c'"
を実行すると、glob'*.c'
が'something.c'
に展開され、find
プライマリ-name 'something.c'
もそのファイルと一致し、出力されます。
touch "'namewithcharset[a].c'"
を実行すると、グロブ'*.c'
はシェルによって拡張されますが、find
プライマリ-name 'namewithcharset[a].c'
はそれ自体と一致しませんnot-それは'namewithcharseta.c'
のみと一致します、存在しないため、-print0
に到達しません。
touch "'x.c'" "'y.c'"
を実行すると、glob '*.c'
はbothファイル名に展開されます。これにより、'y.c'
は有効なプライマリではないため、find
からエラーが出力されます。 (そして、ハイフンで始まらないので、そうすることはできません)。
nullglob
オプションが設定されている場合、異なる動作が発生します。
参照:
すでに言われていることに加えて、あなたはする必要があります:
$files
_変数を配列として宣言します。デフォルトではスカラーになり、スカラーの_var+=something
_は文字列の連結(またはスカラーにinteger属性が指定されている場合は算術加算)を行います。 )。または、var+=(something)
構文を使用します(これにより、変数が自動的に配列に変換されます)。行うこと:
_files=()
while ...
files+=$file # or files+=("$file")
done
_
files
変数が以前にスクリプトの前半で連想配列として宣言されていない限り、十分です(この場合、_files+=something
_は_files["0"]+=something
_のようになり、files+=("$files")
はエラーになります)。
スクリプトの前半でfiles
が連想配列として定義されていないことを保証できない場合は、次のことが必要になる場合があります。
_typeset -a files=()
_
代わりに、変数のスコープを囲んでいる関数に制限するという副作用があります。 typeset -ga files=()
は、グローバルスコープで変数を宣言するため、bash
での回避策として適切に機能しません。 unset files; files=()
は、_unset files
_が設定を解除する代わりに、外部スコープ(連想配列の場合もある)からfiles
変数を明らかにする場合があるため機能しない場合があります。
2番目のスクリプトでいくつかの変更を行う必要があります。欠落している_-name
_を挿入し、一重引用符を削除し、set -fを使用して、アスタリスクの展開を回避します。スクリプトの開始時または(示されているように)サブシェル内のいずれか:
_<(set -f; find $find_args)
_
エコーをprintfに変更し、実際に値の配列を持つようにfiles+=("$file")
として割り当てます。あなたが持っているものは文字列を構築するだけです。
_#!/bin/bash
find_args="-type f -name *.c -print0"
while IFS= read -r -d '' file; do
files+=("$file")
done < <(set -f; find $find_args)
printf '<%s> ' "${files[@]}"; echo
_