シェル変数を引用符で囲むのはいつですか?


184

誰かがシェルスクリプトで変数を引用符で囲む必要があるかどうかを教えてもらえますか?

たとえば、次は正しいですか。

xdg-open $URL 
[ $? -eq 2 ]

または

xdg-open "$URL"
[ "$?" -eq "2" ]

もしそうなら、なぜですか?



この質問は多くの重複を取得しますが、その多くは変数に関するものではないため、「変数」ではなく「値」というタイトルを付けました。これにより、より多くの人がこのトピックを見つけやすくなると思います。
tripleee 2017年

1
@codeforester元に戻された編集はどうなっていますか?
tripleee 2017


回答:


131

一般的な規則:空であるか、スペース(または実際には空白)または特殊文字(ワイルドカード)を含むことができる場合は、引用符で囲みます。スペースで文字列を引用しないと、多くの場合、シェルが単一の引数を複数に分解します。

$?数値なので、引用符は必要ありません。$URLそれが必要かどうかは、そこで許可するものと、それが空の場合でも引数が必要かどうかによって異なります。

なぜなら、その方がより安全だからです。


2
「スペース」は実際には「任意の空白」を意味することに注意してください。
ウィリアムパーセル

4
@クリスチャン:変数の内容がわからない場合は、引用符で囲んだほうが安全です。私はpaxdiabloと同じ原則に従う傾向があり、すべてを引用することを習慣にします(特別な理由がない限り)。
Gordon Davisson、

11
IFSの価値がわからない場合は、どんなことでも引用してください。もしならIFS=0、それecho $?は非常に驚くべきことです。
Charles Duffy

3
予想される値ではなく、コンテキストに基づいて引用してください。そうしないと、バグが悪化します。たとえば、パスにスペースが含まれていないことが確実であるためcp $source1 $source2 $dest、を書くことができると思いますが、予期destしない理由で設定されない場合、3番目の引数は表示さsource1source2ず、暗黙的にコピーされます。空白の宛先に対する適切なエラー(各引数を引用符で囲んだ場合と同様)。
Derek Veit

3
quote it if...思考プロセスは逆です。引用符は必要なときに追加するものではなく、必要なときに削除するものです。二重引用符を使用する必要がある場合(例:変数を展開する場合)または引用符を使用する必要がない場合(例:グロビングとファイル名の展開を行う場合)を除き、文字列とスクリプトは常に一重引用符で囲みます。
Ed Morton

92

要するに、トークン分割とワイルドカード拡張を実行するためにシェルを必要としないすべてのものを引用します。

一重引用符は、それらの間のテキストを逐語的に保護します。シェルが文字列にまったく触れないようにする必要がある場合、これは適切なツールです。通常、これは、変数補間が必要ない場合に選択する引用メカニズムです。

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

変数の補間が必要な場合は、二重引用符が適しています。適切な適応により、文字列に単一引用符が必要な場合の回避策としても適しています。(一重引用符の内側に一重引用符をエスケープする簡単な方法はありません。一重引用符の中にエスケープメカニズムがないためです。存在する場合、完全に逐語的に引用されません。)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

シェルがトークン分割やワイルドカード拡張を実行する必要がある場合、引用符は適切ではありません。

トークン分割;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

対照的に:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(ループは、単一の引用符付き文字列に対して1回だけ実行されます。)

 $ for word in '$words'; do echo "$word"; done
 $words

(ループは、リテラルの単一引用符で囲まれた文字列に対して1回だけ実行されます。)

ワイルドカード展開:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

対照的に:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(文字通りの名前のファイルはありませんfile*.txt。)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(という名前のファイルもありません$pattern!)

具体的には、通常、ファイル名を含むものはすべて引用符で囲む必要があります(ファイル名には空白やその他のシェルメタ文字を含めることができるため)。通常、URLを含むものはすべて引用符で囲む必要があります(多くのURLには?、などのシェルメタ文字が含まれているため&)。正規表現を含むものは通常、引用符で囲む必要があります(同上)。空白以外の文字間の単一のスペース以外の重要な空白を含むものは引用符で囲む必要があります(そうしないと、シェルは空白を効果的に単一のスペースに変換し、先頭または末尾の空白を削除します)。

変数にシェルのメタ文字を含まない値のみを含めることができることがわかっている場合、引用符は省略可能です。したがって、$?この変数には1つの数値しか含めることができないため、引用符で囲まれていない場合は基本的に問題ありません。ただし、これ"$?"も正しく、一般的な一貫性と正確さのために推奨されています(これは私の個人的な推奨事項であり、広く認知されているポリシーではありません)。

変数ではない値は基本的に同じルールに従いますが、メタ文字を引用する代わりにエスケープすることもできます。一般的な例では&、メタ文字がエスケープまたは引用符で囲まれていない限り、inを含むURL はシェルによってバックグラウンドコマンドとして解析されます。

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(もちろん、これは、URLが引用符で囲まれていない変数にある場合にも発生します。)静的文字列の場合、一重引用符が最も意味がありますが、引用符やエスケープの形式はすべてここで機能します。

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

最後の例は、私が「シーソークォーティング」と呼んでいる別の有用な概念も示唆しています。一重引用符と二重引用符を混在させる必要がある場合は、互いに隣接して使用できます。たとえば、次の引用符付き文字列

'$HOME '
"isn't"
' where `<3'
"' is."

背中合わせに貼り付けて、トークン化と引用の削除後に単一の長い文字列を形成できます。

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

これはひどく読みにくいですが、それは一般的な技術なので、知っておくと良いでしょう。

余談ですが、スクリプトは通常ls、何にも使用しないでください。 ワイルドカードを展開するには、単に...使用します。

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(後者の例ではループは完全に不要です。printf特に、複数の引数で正常に機能しますstat。ただし、ワイルドカード一致でのループは一般的な問題であり、頻繁に正しく行われません。)

ループするトークンのリストや展開するワイルドカードを含む変数はあまり見られないため、「何をしているのか正確に知らない限り、すべてを引用する」と省略されることがあります。


1
これは、私が関連する質問に投稿した回答(の一部)の変形です。これは簡潔であり、この特定の問題の標準的な質問になるほど十分に定義されているため、ここに貼り付けています。
tripleee 2014

4
これはアイテム#0であり、一般的なBashの間違いのmywiki.wooledge.org/BashPitfallsコレクションの繰り返しテーマであることに注意します。そのリストの個々の項目の多くは、基本的にこの問題に関するものです。
tripleee 2017年

27

一般的な引用の3点式は次のとおりです。

二重引用符

単語の分割とグロビングを抑制したい場合。また、リテラルを正規表現ではなく文字列として処理する必要があるコンテキストでも使用できます。

単一引用符

文字列リテラルで、バックスラッシュの補間と特別な処理を抑制したい場合。つまり、二重引用符を使用することが不適切な状況です。

引用なし

単語の分割またはグロビングの問題がないことが確実であるか、単語の分割およびグロビングが必要な場合


二重引用符

  • 空白を含むリテラル文字列("StackOverflow rocks!""Steve's Apple"
  • 変数展開("$var""${arr[@]}"
  • コマンド置換("$(ls)""`ls`"
  • ディレクトリパスまたはファイル名の部分にスペースが含まれているグロブ("/my dir/"*
  • 単一引用符を保護する("single'quote'delimited'string"
  • Bashパラメータ展開("${filename##*/}"

単一引用符

  • 空白を含むコマンド名と引数
  • 補間を抑制する必要があるリテラル文字列('Really costs $$!''just a backslash followed by a t: \t'
  • 二重引用符を保護する('The "crux"'
  • 補間を抑制する必要がある正規表現リテラル
  • 特殊文字($'\n\t')を含むリテラルにはシェル引用を使用します
  • 複数の単一引用符と二重引用符を保護する必要がある場合は、シェル引用を使用します($'{"table": "users", "where": "first_name"=\'Steve\'}'

引用なし

  • 周りの標準数値変数($$$?$#など)
  • 、、のような算術コンテキスト((count++))"${arr[idx]}""${string:start:length}"
  • [[ ]]単語の分割やグロビングの問題のない内部表現(これはスタイルの問題であり、意見は大きく異なる可能性があります)
  • 単語分割が必要な場所(for word in $words
  • グロビングが必要な場所(for txtfile in *.txt; do ...
  • (ではなく)~として解釈したい場所$HOME~/"some dir""~/some dir"

以下も参照してください。


3
これらのガイドラインによると、"ls" "/" 「すべての文字列コンテキスト」というフレーズは、より慎重に修飾する必要があります。
William Pursell 2017

5
では[[ ]]、引用符は=/ ==およびの右側で問題になり=~ます。これは、文字列をパターン/正規表現として解釈するか、文字どおりに解釈するかによって違いがあります。
ベンジャミンW.

6
良い概要ですが、@ BenjaminW。のコメントは統合する価値があり、ANSI Cで引用された文字列($'...')には必ず独自のセクションがあります。
mklement0 2017年

3
@ mklement0、確かにそれらは同等です。これらのガイドラインは"ls" "/"、より一般的なの代わりに常に入力する必要があることを示してls /おり、私はそれをガイドラインの大きな欠陥として捉えています。
William Pursell 2017年

4
以下のために引用符なしあなたは、変数の割り当てまたは追加される場合がありますcase:)
PesaThe

4

スペースが含まれ"$var"$varいないことが確実でない限り、私は通常、安全のためにquoted like を使用しています。

$var行を結合する簡単な方法として使用します。

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped

最後のコメントは誤解を招く可能性があります。改行は、単に削除されるのではなく、スペースで効果的に置き換えられます。
tripleee

-1

シェルスクリプトで変数を使用するには、 ""で囲まれた変数を使用します。引用符で囲まれた変数は、変数にスペースまたは特殊文字が含まれていても、シェルスクリプトの実行に影響しないことを意味します。変数名にスペースや特殊文字が含まれていないことが確実な場合は、 ""なしで使用できます。

例:

echo "$ url name"-(いつでも使用できます)

echo "$ url name"-(このような状況では使用できないため、使用する前に注意してください)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.