web-dev-qa-db-ja.com

引用符で囲まれた変数のオプションが失敗するのに、引用符で囲まれていない場合は機能するのはなぜですか?

私は、bashで変数を引用する必要があることを読みました。 $ fooの代わりに "$ foo"。しかし、スクリプトを書いているときに、引用符なしでは機能するが引用符なしでは機能するケースを見つけました。

wget_options='--mirror --no-Host-directories'
local_root="$1" # ./testdir recieved from command line
remote_root="$2" # ftp://XXX recieved from command line 
relative_path="$3" # /XXX received from command line

これは機能します:

wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

これはありません($ wget_optionsにある二重引用符に注意してください):

wget "$wget_options" --directory_prefix="$local_root" "$remote_root$relative_path"
  • これの理由は何ですか?

  • 最初の行は良いバージョンです。または、この動作を引き起こす隠れたエラーがどこかにあると思いますか?

  • 一般に、bashとその引用がどのように機能するかを理解するための優れたドキュメントはどこにありますか?このスクリプトを書いているとき、ルールを理解するのではなく、試行錯誤を繰り返し始めたように感じます。

18
z32a7ul

基本的に、変数の展開を二重引用符で囲んで、Wordの分割(およびファイル名の生成)から保護する必要があります。しかし、あなたの例では、

wget_options='--mirror --no-Host-directories'
wget $wget_options --directory_prefix="$local_root" "$remote_root$relative_path"

単語分割はまさにあなたが望むものです

"$wget_options"(引用符付き)、wgetは単一の引数--mirror --no-Host-directoriesと文句を言う

wget: unknown option -- mirror --no-Host-directories

wgetで2つのオプションを表示するには--mirrorおよび--no-Host-directories独立しているため、Word分割を行う必要があります。

これを行うには、より堅牢な方法があります。 bashまたはbashのような配列を使用する他のシェルを使用している場合は、 glenn jackman's answer を参照してください。 ギレスの回答 は、標準の/bin/sh。どちらも基本的に、各オプションを個別の要素として配列に格納します。

関連する質問と適切な回答: シェルスクリプトが空白やその他の特殊文字で詰まるのはなぜですか?


変数展開を二重引用符で囲むのが良い目安です。 そうする。次に、ごく少数の場合に注意してください。これらは、上記のエラーメッセージなどの診断メッセージを通じて表示されます。

変数の展開を引用する必要がない必要がないいくつかのケースもあります。ただし、あまり大きな違いはないため、二重引用符を使い続ける方が簡単です。そのようなケースの1つは

variable=$other_variable

もう一つは

case $variable in
    ...) ... ;;
esac
28
Kusalananda

配列を使用するコーディングの最も堅牢な方法:

wget_options=(
    --mirror 
    --no-Host-directories
    --directory_prefix="$1"
)
wget "${wget_options[@]}" "$2/$3"
33
glenn jackman

文字列変数に文字列のリストを保存しようとしています。合いません。変数にアクセスする方法に関係なく、何かが壊れています。

wget_options='--mirror --no-Host-directories'は、変数wget_optionsにスペースを含む文字列を設定します。この時点では、スペースがオプションの一部であるか、オプション間のセパレータであるかを知る方法はありません。

引用符付きの置換wget "$wget_options"を使用して変数にアクセスすると、変数の値が文字列として使用されます。これは、wgetに単一のパラメーターとして渡されるため、単一のオプションであることを意味します。これは、複数のオプションを意味することを意図していたため、あなたのケースではうまくいきません。

引用符で囲まれていない置換wget $wget_optionsを使用すると、文字列変数の値は、「split + glob」というニックネームの付いた展開プロセスを受けます。

  1. 変数の値を取り、空白で区切られた部分に分割します($IFS変数を変更していない場合)。これにより、文字列の中間リストが作成されます。
  2. 中間リストの各要素について、それが1つ以上のファイルと一致するワイルドカードパターンである場合、その要素を一致するファイルのリストで置き換えます。

これは、分割プロセスがスペースをセパレーターに変換するため、例では機能しますが、オプションにスペースとワイルドカード文字を含めることができるため、一般的に機能しません。

Ksh、bash、yash、zshでは、配列変数を使用できます。シェル用語の配列は文字列のリストなので、情報が失われることはありません。配列変数を作成するには、変数に値を割り当てるときに配列要素を括弧で囲みます。配列のすべての要素にアクセスするには、"${VARIABLE[@]}"を使用します—これは、配列の要素からリストを形成する"$@"の一般化です。ここでも二重引用符が必要であることに注意してください。そうでない場合、各要素はsplit + globを受けます。

wget_options=(--mirror --no-Host-directories --user-agent="I can haz spaces")
wget "${wget_options[@]}" …

単純なshでは、配列変数はありません。位置引数を失ってもかまわない場合は、それらを使用して文字列の1つのリストを保存できます。

set -- --mirror --no-Host-directories --user-agent="I can haz spaces"
wget "$@" …

詳細については、 なぜシェルスクリプトが空白やその他の特殊文字で詰まるのですか? を参照してください。