二重引用符はいつ必要ですか?


120

以前は$VARIABLE、少なくともシェルが単一のアイテムとして解釈することを望んでいた場合、を含む式を二重引用符で囲む$VARIABLEことを推奨していました。

ただし、シェルの最近のバージョンでは、二重引用符は必ずしも必要ではありません(少なくとも上記の目的のため)。例えば、中bash

% FOO='bar baz'
% [ $FOO = 'bar baz' ] && echo OK
bash: [: too many arguments
% [[ $FOO = 'bar baz' ]] && echo OK
OK
% touch 'bar baz'
% ls $FOO
ls: cannot access bar: No such file or directory
ls: cannot access baz: No such file or directory

ではzsh、他の一方で、同じ3つのコマンドが成功しました。したがって、この実験に基づいて、でbash二重引用符を省略できますが[[ ... ]]、内部で[ ... ]もコマンドライン引数でzshも省略できませんが、で、二重引用符はこれらすべての場合に省略できます。

しかし、上記のような逸話的な例から一般的なルールを推測することは簡単な命題です。二重引用符が必要な場合の要約を見るといいでしょう。私は中に主に興味があるzshbash/bin/sh


10
zshで観察される動作は設定に依存し、SH_WORD_SPLITオプションの影響を受けます。
ウルリッヒダンゲル


3
余談ですが、すべて大文字の変数名は、オペレーティングシステムとシェルにとって意味のある変数によって使用されます。POSIX仕様では、アプリケーション定義変数に小文字の名前を使用することを明示的に推奨しています。(引用された仕様は特に環境変数に焦点を当てていますが、環境変数とシェル変数は名前空間を共有します:環境変数で既に使用されている名前でシェル変数を作成しようとすると、後者が上書きされます)。pubs.opengroup.org/onlinepubs/009695399/basedefs/…の 4番目の段落を参照してください。
チャールズダフィー

回答:


128

まず、zshを残りから分離します。古いシェルと最新のシェルの問題ではありません。zshの動作は異なります。zshデザイナーは、従来のシェル(Bourne、ksh、bash)との互換性をなくし、使いやすくすることにしました。

第二に、二重引用符をいつ使用するかは、いつ必要かを覚えるよりもずっと簡単です。それらはほとんどの場合必要であるため、必要なときではなく、必要でないときを学ぶ必要があります。

一言で言えば、単語のリストまたはパターンが予想される場合は常に二重引用符が必要です。生の文字列がパーサーによって予期されるコンテキストでは、これらはオプションです。

引用符なしで何が起こるか

二重引用符がないと、2つのことが起こります。

  1. 最初に、展開の結果(などのパラメーター置換の変数の値${foo}、またはのようなコマンド置換のコマンドの出力$(foo))は、空白が含まれているすべての場所で単語に分割されます。
    より正確には、展開の結果は、IFS変数の値に含まれる各文字(区切り文字)で分割されます。区切り文字のシーケンスに空白(スペース、タブ、または改行)が含まれる場合、空白は単一の文字としてカウントされます。先頭、末尾、または繰り返される非空白区切り文字は、空のフィールドにつながります。例えば、とIFS=" :":one::two : three: :four 前に空のフィールドを生成するone間、onetwoの間、および(単一のもの)threefour
  2. 分割の結果の各フィールドは、文字のいずれかを含む場合、グロブ(ワイルドカードパターン)として解釈され\[*?ます。そのパターンが1つ以上のファイル名に一致する場合、パターンは一致するファイル名のリストに置き換えられます。

引用符で囲まれていない変数展開$fooは、「split + glob operator」として通俗的に知られて"$foo"いますが、対照的に変数の値を取得するだけですfoo。コマンド置換についても同じことが言えます。"$(foo)"コマンド置換で$(foo)あり、コマンド置換の後にsplit + globが続きます。

二重引用符を省略できる場所

以下に、二重引用符なしで変数またはコマンド置換を記述できるBourneスタイルのシェルで考えられるすべてのケースを示します。値は文字どおりに解釈されます。

  • 割り当ての右側。

    var=$stuff
    a_single_star=*
    

    exportキーワードではなく、通常の組み込み関数であるため、の後に二重引用符が必要なことに注意してください。これは、ダッシュ、zsh(shエミュレーション)、yash、またはposhなどの一部のシェルでのみ当てはまります。bashとkshは両方ともexport特別に扱います。

    export VAR="$stuff"
  • case声明。

    case $var in 

    ケースパターンには二重引用符が必要であることに注意してください。単語の分割はケースパターンでは発生しませんが、引用符で囲まれていない変数はパターンとして解釈され、引用符で囲まれた変数はリテラル文字列として解釈されます。

    a_star='a*'
    case $var in
      "$a_star") echo "'$var' is the two characters a, *";;
       $a_star) echo "'$var' begins with a";;
    esac
    
  • 二重括弧内。二重括弧は、シェルの特別な構文です。

    [[ -e $filename ]]

    パターンまたは正規表現が予想される場所で二重引用符が必要な場合を除きます:=or ==または!=orの右側=~

    a_star='a*'
    if [[ $var == "$a_star" ]]; then echo "'$var' is the two characters a, *"
    elif [[ $var == $a_star ]]; then echo "'$var' begins with a"
    fi
    

    二重引用符は、通常の[ … ]シェル構文(たまたま呼び出されるコマンドです[)であるため、通常は単一の括弧内に二重引用符が必要です。単一または二重括弧を参照してください

  • 非対話型POSIXシェルでのリダイレクト(bashではなくksh88)。

    echo "hello world" >$filename

    一部のシェルは、対話型の場合、変数の値をワイルドカードパターンとして扱います。POSIXは非対話型シェルでのその動作を禁止していますが、bash(POSIXモードを除く)やksh88(shSolarisなどの一部の商用Unicesの(おそらく)POSIXとして検出された場合を含む)を含むいくつかのシェルはまだそこにあります(分割bashしようとしますそして、その分割+グロビングが正確に1つの単語にならない限りリダイレクトは失敗します)、それはshあなたがbashスクリプトにいつかそれをスクリプトに変換したい場合はスクリプトでリダイレクトのターゲットを引用する方が良い理由shですその点で非準拠であるか、対話型シェルから供給される場合があります。

  • 算術式の中。実際、変数を算術式として解析するには、引用符を省略する必要があります。

    expr=2*2
    echo "$(($expr))"
    

    ただし、ほとんどのシェルではPOSIXが必要とする(!?)

  • 連想配列の添字。

    typeset -A a
    i='foo bar*qux'
    a[foo\ bar\*qux]=hello
    echo "${a[$i]}"
    

引用符で囲まれていない変数とコマンド置換は、まれな状況で役立つ場合があります。

  • 変数値またはコマンド出力がグロブパターンのリストで構成されており、これらのパターンを一致するファイルのリストに拡張する場合。
  • 値にワイルドカード文字が含まれていないことがわかっている場合、それ$IFSは変更されず、空白文字で分割する必要があります。
  • 特定の文字で値を分割する場合:でグロビングを無効にし、区切り文字にset -f設定IFSして(または空白のままで分割するにはそのままにして)、展開します。

Zsh

zshでは、いくつかの例外を除いて、ほとんどの場合二重引用符を省略できます。

  • $var複数の単語に展開されることはありませんが、値がvar空の文字列の場合、空のリストに展開されます(単一の空の単語を含むリストとは対照的です)。コントラスト:

    var=
    print -l $var foo        # prints just foo
    print -l "$var" foo      # prints an empty line, then foo
    

    同様に、"${array[@]}"配列のすべての要素に展開しますが、$array空でない要素にのみ展開します。

  • @パラメータ展開フラグは時々全体の置換の周りに二重引用符が必要です"${(@)foo}"

  • 引用符で囲まれていない場合は、コマンド置換は、フィールド分割を受ける:echo $(echo 'a'; echo '*')プリントa *(単一のスペースを伴う)のに対しecho "$(echo 'a'; echo '*')"、未修飾2行の文字列を印刷します。"$(somecommand)"最後の改行を除いて、単一の単語でコマンドの出力を取得するために使用します。"${$(somecommand; echo _)%?}"最終改行を含むコマンドの正確な出力を取得するために使用します。"${(@f)$(somecommand)}"コマンドの出力から行の配列を取得するために使用します。


実際、変数を算術式として解析するには、引用符を省略する必要があります。引用符を使用してサンプルを機能させることができる理由:echo "$(("$expr"))"
Cyker

これは何ですman bash:言う、それは二重引用符であるかのように表現が扱われますが、括弧の内側のダブルクォートが特別扱いされていません。
サイカー

4
また、興味がある人にとっては、split + globの正式な名前は単語の分割パス名の拡張です。
サイカー

3
FYI - オーバーStackOverflowの上、私が持っていた誰かが引数を引用していない守るために、この答えうちに「オプションのraw文字列が期待されている」言語を引きますecho。言語をさらに明確にしようとする価値があるかもしれません(「パーサーが生の文字列を予期している場合」、おそらく?)
チャールズダフィー

2
@CharlesDuffyうーん、私はこの誤読を考えていませんでした。「where」を「when」に変更し、あなたが提案したように文を補強しました。
ジル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.