web-dev-qa-db-ja.com

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

以前のアドバイスでは、少なくともシェルが単一のアイテムとして解釈したい場合は、_$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_に興味があります。

127
kjo

まず、zshを残りの部分から分離します。古いシェルと最新のシェルの問題ではありません。zshの動作は異なります。 zsh設計者は、従来のシェル(Bourne、ksh、bash)との互換性をなくすことを決定しましたが、使いやすくなっています。

第2に、二重引用符がいつ必要かを覚えておくよりも、常に二重引用符を使用する方がはるかに簡単です。ほとんどの場合、これらは必要なので、必要なときではなく、必要でないときに学習する必要があります。

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

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

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

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

引用符で囲まれていない変数展開_$foo_は、変数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 ]]
    _

    ただし、パターンまたは正規表現が必要な場合は、二重引用符が必要です。ただし、_=_または_==_または_!=_または_=~_の右側にあります。

    _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(Solarisのようないくつかの商用Unicesの(おそらく)POSIX shとして検出された場合を含む)を含むいくつかのシェルはまだそこで動作します(bashも試行splittingし、リダイレクトが失敗した場合に限りsplit + globbingが1つのWordになるため)、必要に応じて、shスクリプトでリダイレクトのターゲットを引用する方が適切です。いつかbashスクリプトに変換するか、その時点でshが非準拠であるシステムで実行するか、インタラクティブシェルからsourcedにすることができます。

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

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

    ただし、算術展開を囲む引用符は、POSIXが必要とするほとんどのシェルでWord分割の対象となるため必要です(!?)。

  • 連想配列の添え字。

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

引用符で囲まれていない変数とコマンド置換は、いくつかのまれな状況で役立ちます。

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

Zsh

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

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

    _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)"を使用して、コマンドの出力を最終的な改行ではなく単一のWordで取得します。 "${$(somecommand; echo _)%?}"を使用して、最終改行を含むコマンドの正確な出力を取得します。コマンドの出力から行の配列を取得するには、"${(@f)$(somecommand)}"を使用します。