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

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

過去の古いアドバイスは、少なくとも$VARIABLEシェルがそれを単一の項目として解釈したい場合は、aに関連するすべての式を二重引用符で囲むことでした。そうしないと、内容の空白が原因で$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場合に二重引用符を省略できるようです。

しかし、上記の逸話的な例では、一般的な規則を推論するのは危険な提案です。二重引用符が必要な場合の概要を見るのが良いでしょう。私は主にzsh、、bashに興味があります/bin/sh

答え1

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

第二に、必要に応じて覚えているよりも常に二重引用符を使用する方がはるかに簡単です。ほとんどの場合、必要なので必要な時ではなく、必要ないときに学ぶ必要があります。

要するに、単語リストまたはパターンが必要な場合は必ず二重引用符が必要です。。パーサーが単一の生の文字列を期待する状況ではオプションです。

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

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

  1. $fooまず、拡張結果(orなどの引数置換の変数値、${foo}またはなどのコマンド置換のコマンド出力$(foo))をスペースを含む単語に分割します。より正確には、拡張結果は
    変数valueに表示されるIFSすべての文字(区切り文字)で分割されます。区切り文字シーケンスにスペース(スペース、タブ、または改行)が含まれている場合、スペースは単一文字としてカウントされます。空白以外の区切り文字は、前、後、または繰り返され、空のフィールドになります。たとえば、(スペースとコロン)を使用すると、IFS=" :"前、間、および間に空の:one::two : three: :four フィールドが作成されます。oneonetwothreefour
  2. 分割によって生成された各フィールドに文字の1つが含まれている場合、そのフィールドはglob(ワイルドカードパターン)として解釈されます[*?。パターンが 1 つ以上のファイル名と一致する場合、パターンは一致するファイル名のリストに置き換えられます。

引用符のない変数拡張は、変数の値を取るのとは対照的に、$foo「split + glob演算子」として口語的に知られています。コマンドの置換も同様です。はいコマンドの置き換え、はいコマンドの置換の後に分割+globが続きます。"$foo"foo"$(foo)"$(foo)

二重引用符はどこで省略できますか?

以下は、二重引用符なしで変数やコマンド置換を作成し、値を文字通り解釈できるBourneスタイルのシェルで考えられるすべてのケースです。

  • スカラー(非配列)変数の割り当ての右側。

     var=$stuff
     a_single_star=*
    

    exportorの後に二重引用符が必要であることに注意してください。readonly一部のシェルでは、まだキーワードではなく、一般的な組み込み関数であるためです。これは、一部の古いバージョンのダッシュ、以前のバージョンのzsh(shエミュレーション)、yash、またはposhなどの一部のシェルでのみ発生します。最新バージョンのbash、ksh、dash、zshでは、export/readonlyとcoは次のように特別に処理されます。 POSIXでは、これはより明示的に要求されるので、二重組み込み/キーワード(特定の条件下で)です。

     export VAR="$stuff"
    
  • 声明からcase

     case $var in …
    

    実際、大文字と小文字のパターンには二重引用符が必要です。大文字と小文字のパターンでは単語の区切りはありませんが、引用符のない変数はglobスタイルのパターンとして解釈され、引用符付きの変数はリテラル文字列として解釈されます。

     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(sh一部の商用Unices(Solarisなど)に対してPOSIXであることが知られている場合を含む)を含む一部のシェルはまだこれを行います(bashまた、してください)分けるそれ以外の場合、リダイレクトは失敗します。スプリット+ワイルドカードsh結果はちょうど1ワードです)ので、いつかスクリプトに変換したい場合、またはbashそれに準拠していないシステムで実行したい場合に備えて、スクリプトからリダイレクト先を参照することをお勧めします。またはsh多分源泉インタラクティブシェルから。

  • 算術式内で。実際、複数のシェルで変数を算術式に解析するには、引用符を省略する必要があります。

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

    ただし、算術拡張には引用符が必要です。これは、ほとんどのシェル(!?)でPOSIXが要求するようにトークン化を実行するためです。

  • 連想配列添え字から。

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

引用符のない変数とコマンドの置換は、次のようなまれな状況で役に立ちます。

  • 変数値またはコマンド出力にglobパターンのリストが含まれており、そのパターンを一致するファイルのリストに拡張する場合。
  • 値にワイルドカード文字が含まれていないことがわかると、値は変更されず、$IFSスペース(スペース、タブ、および改行)文字に分割しようとします。
  • 特定の文字から値を分割したい場合:set -o noglob/ disableワイルドカードを使用set -fし、区切り文字に設定してからIFS(またはスペースで分割されるようにしてから)展開します。

ジッシュ

zshではほとんど二重引用符を省略できますが、いくつかの例外があります。

  • $var決して複数の単語には展開されません(配列ではないと仮定)。ただし、値が空の文字列の場合、空のvarリスト(単一の空の単語を含むリストではない)に展開されます。var比較:

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

    同様に"${array[@]}"、配列内のすべての要素に展開されますが、$arraynull以外の要素にのみ拡張されます。

  • kshやbashのように、[[ … ]]or演算子の右側にある変数は、文字列が含まれている場合は二重引用符で囲む必要があります。パターン/正規表現が含まれている場合は、二重引用符で囲む必要はありません。 :本当ですが、偽です。==!==~p='a*'; [[ abc == $p ]]p='a*'; [[ abc == "$p" ]]

  • パラメータ@拡張フラグでは、置換項目全体を二重引用符で囲む必要があります"${(@)foo}"

  • 引用符がない場合、コマンド置換はフィールド分割を経ます。代わりに、echo $(echo 'a'; echo '*')印刷a *(単一スペースを使用)はecho "$(echo 'a'; echo '*')"変更されていない2行の文字列を印刷します。"$(somecommand)"最後に改行文字なしでコマンドの単語を出力するために使用されます。"${$(somecommand; echo .)%?}"最後の改行を含むコマンドの正確な出力を得るために使用されます。"${(@f)$(somecommand)}"コマンド出力から行配列を取得するために使用されます(後に空白行がある場合は削除)。

関連情報