Bash:サブシェルからエスケープされた引用符

Bash:サブシェルからエスケープされた引用符

次のコマンドを実行するとき:

#!/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番目のシーンを1番目のシーンと同じように変更するにはどうすればよいですか?

私の理解は、二重引用符の中に一重引用符があるため、一重引用符がエスケープされ、次のような誤った拡張が生成されることです。

find -type f -name ''\''*.c'\'' -print0

答え1

ブレア首相の回答正確ですが、ここで実際に何が起こっているのかを分析するには(欠落しているmasterデータベースのスペルエラーを無視して-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はいいえ拡張してください。ただし、一重引用符は削除されます。したがって、findプロセスの開始時に表示されるパラメータのリストは次のとおりです。

-type
f
-name
*.c
-print0

これらのパラメータは区切り文字で区切られます。ヌルバイト、空白か改行なし。これはシェルレベルではなくCレベルにあります。これは、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パラメータ拡張が適用されます。続いて噴射続いてパス名拡張(別名グローバル拡張)。

パラメータ拡張後は-type f -name '*.c' -print0後ろに参照が削除されました。したがって、一重引用符は削除されません。

単語を分割すると、次のような個々の単語が得られます。

-type
f
-name
'*.c'
-print0

それからパス名拡張。もちろん、'*.c'通常はファイル名に一重引用符を入れないので、どんなものとも一致する可能性がないので、結果は次のようになります。可能'*.c'リテラルモードで渡されるため、findプライマリ-nameデータベースはすべてのファイルで失敗します。 (名前が一重引用符で始まり、3文字で終わるファイルがある場合にのみ成功します.c'。)


編集:実際にそのようなファイルが存在する場合、globは'*.c'そのファイルや他のそのようなファイルと一致するように拡張されます。拡張[実際のファイル名]find柄。 したがって、-print0マスタノードに到達するかどうかは、(a)一つそのようなファイル名と(b)対応するファイル名(globとして解釈される)が一致するかどうか。

例:

touch "'something.c'"その時走ると全体的な状況 '*.c'展開すると、'something.c'デフォルトのfindファイル-name 'something.c'も一致して印刷されます。

実行すると、touch "'namewithcharset[a].c'"globは'*.c'シェルによってglobに拡張されますが、findデフォルトは-name 'namewithcharset[a].c'いいえ一致自体 - 一致のみが'namewithcharseta.c'存在しないため、-print0到達できません。

を実行すると、touch "'x.c'" "'y.c'"globは'*.c'次に展開されます。両方findfilename は有効なデフォルトのファイル名ではないため、出力エラーを引き起こす可能性があります'y.c'(そしてハイフンで始まらないので、それはできません)。


nullglobこのオプションを設定すると、他の動作が発生します。

また見なさい:

答え2

-name(スペルエラーがあります。2番目の例ではフラグを省略しました。)

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[@]}配列内のすべての要素に展開され、各要素は単一の文字列には拡張されず、別々の単語です。これは元のスクリプトの意図に近いです。

答え3

言ったことに加えて、以下が必要です。

  • 変数は$filesデフォルトではスカラーなので、配列として宣言し、スカラーvar+=somethingに対して文字列連結(またはスカラーが割り当てられている場合は算術加算)を実行します。整数属性)。または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=()ため、問題の回避策として正しく機能しません。場合によっては、変数を設定解除するのではなく、外部範囲(連想配列)で変数を表示することが可能なため、機能しないことがあります。bashunset files; files=()unset filesfiles

関連情報