引用符で囲まれた変数のオプションが失敗するのはなぜですか?


18

私は、bfooで変数を引用する必要があることを読みました。たとえば、$ 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とその引用がどのように機能するかを理解するための優れたドキュメントはどこにありますか?このスクリプトを書いている間、私はルールを理解するのではなく、試行錯誤のベースに取り組み始めたと感じています。


3
あなたの質問はここに答えられます:mywiki.wooledge.org/BashFAQ/050
グレンジャックマン

3
ルールのソースであるbashマニュアルに移動します。セクション3.5「シェル拡張」、特に単語の分割とファイル名の拡張に細心の注意を払ってください。これら2つの要因は、引用符を使用して制御するものです。
グレンジャックマン


4
低レベルでコマンドライン引数がどのように機能するかを理解するのに役立つと思います。プログラムが実行されると、文字のリストのリストとして引数を受け取ります(十分近い)。各内部リストは、「引数」と呼ばれるものです。ほとんどのプログラムは、引数間の論理的な分離に依存しています。ここでは、(1つの引数として)意味がwgetわかりませんが--mirror --no-host-directories2つの引数に分割されたときに処理されます。引数ベクトル内にあるスペースと引用符を特別に扱うプログラムはほとんどありません。問題はbash
、、

2
>人間が使用します。引数間の境界を手動で定義するのは面倒なので、シェルは空白で分割して行(文字のリスト)を引数ベクトル(文字のリストのリスト)に変えます。変数展開は最初の展開が行うものの1つなbashので、$aその内容を直接記述するのとまったく同じだと想像できます。これで問題が明らかになりました:にa="-a -b"; cmd "$a"展開されますcmd "-a -b"が、cmdおそらくそれが何を意味するのか分からないでしょう。cmd $aに展開するとcmd -a -b、おそらく動作します。
HTNW

回答:


28

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

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二つのオプションを参照してくださいする--mirror--no-host-directories、別として、単語の分割が発生することがあります。

これを行うより堅牢な方法があります。あなたがdoのbashような配列を使用している、または他のシェルを使用している場合bashは、glenn jackmanの答えを参照してくださいジルの答えは、標準のようなより単純なシェルの代替ソリューションについても説明してい/bin/shます どちらも基本的に、各オプションを配列内の個別の要素として保存します。

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


変数展開を二重引用符で囲むことは、経験則として適切です。 そうしてください。次に、それを行うべきではない非常に少数のケース注意してください。これらは、上記のエラーメッセージなどの診断メッセージを通じて表示されます。

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

variable=$other_variable

もう一つは

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

2
そのsplit + glob演算子を使用する前$IFSに、正しい値が含まれていることを確認する必要があります。ここでスペースで分割する必要があり、テキストにはタブや改行が含まれないため、デフォルト値はそうなります$IFSが、そのコードが$IFS変更された可能性のあるコンテキストで呼び出される可能性のある関数で使用される場合、あなたが設定したいと思います$IFS(コードの残りの部分は無修正を前提とした場合、おそらく後でそれを復元するか、そのためのローカルスコープを使用して事前に$IFS
ステファンChazelas

32

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

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

これは正しい答えです。リファレンス
-l0b0

2
それは良い答えなので、私はそれを支持しましたが、Kusalanda'sは私のコードが間違っていた理由を理解するのにもっと助けてくれて、私は1つだけを受け入れることができます。
z32a7ul

rsyncリストの誰かがこのコンストラクトを見せてくれるまで、私はトラブルの世界にぶつかりました。一部の要素が空の文字列である場合に特に役立ちます。これにより、空の文字列が消えます。cpandのようないくつかのコマンドrsyncなものに展開されると予期しないことをしますrsync '' rest of parameters。これは、コマンドピースを条件ごとに構築し、1か所で一度実行するのに最適です。
ジョー

17

文字列のリストを文字列変数に保存しようとしています。収まりません。どのように変数にアクセスしても、何かが壊れています。

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 "$@" 

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


単純なshの場合、サブシェルは位置引数を保持します(set -- ...; exec wget "$@" ...)
ジョンクーゲルマンはモニカをサポートします
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.