シェルスクリプトで変数を引用符で囲む必要があるかどうかを教えてもらえますか?
たとえば、次は正しいですか?
xdg-open $URL
[ $? -eq 2 ]
または
xdg-open "$URL"
[ "$?" -eq "2" ]
もしそうなら、なぜですか?
一般的なルール:空にするか、スペース(または実際には空白)または特殊文字(ワイルドカード)を含めることができる場合は、引用符で囲みます。文字列をスペースで引用しないと、シェルが単一の引数を多数に分割することがよくあります。
$?
は数値なので、引用符は必要ありません。 $URL
が必要かどうかは、そこで許可する内容と、引数が空の場合でも引数が必要かどうかによって異なります。
文字列を引用する傾向がありますが、それはより安全だからです。
要するに、シェルがトークン分割とワイルドカード拡張を実行する必要がないすべてを引用してください。
一重引用符は、それらの間のテキストを逐語的に保護します。これは、シェルが文字列にまったく触れないようにする必要がある場合に適切なツールです。通常、変数補間を必要としない場合は、クォートメカニズムが最適です。
$ 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つの数値しか含めることができないからです。ただし、"$?"
も適切であり、一般的な一貫性と正確さのために推奨されます(ただし、これは私の個人的な推奨事項であり、広く認知されているポリシーではありません)。
変数ではない値は基本的に同じルールに従いますが、メタ文字を引用する代わりにエスケープすることもできます。一般的な例では、&
を含む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
も同様です。
ループするトークンのリストまたは展開するワイルドカードを含む変数はあまり頻繁に見られないため、「何をしているのか正確にわからない限り、すべてを引用する」と略すことがあります。
一般的な引用の3点式は次のとおりです。
二重引用符
Wordの分割とグロビングを抑制したいコンテキストで。また、リテラルを正規表現ではなく文字列として扱うコンテキストでも。
一重引用符
バックスラッシュの補間と特別な処理を抑制したい文字列リテラル。言い換えると、二重引用符を使用するのが不適切な状況です。
引用符なし
Wordの分割やグロビングの問題がないことを絶対に確信している場合、またはWordの分割とグロビングが必要。
例
二重引用符
"StackOverflow rocks!"
_、_"Steve's Apple"
_)"$var"
_、_"${arr[@]}"
_)"$(ls)"
、_"`ls`"
_)"/my dir/"*
_)"single'quote'delimited'string"
_)"${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}"
_などの算術コンテキストで[[ ]]
_表現の中に、Wordの分割やグロビングの問題がない(これはスタイルの問題であり、意見は大きく異なる可能性がある)for Word in $words
_)for txtfile in *.txt; do ...
_)~
_を_$HOME
_(_~/"some dir"
_として解釈し、_"~/some dir"
_ではなく)として解釈する場合こちらもご覧ください:
"$var"
にスペースが含まれていないことが確実でない限り、安全のために$var
のような引用符を使用します。
行を結合する簡単な方法として$var
を使用します。
lines="`cat multi-lines-text-file.txt`"
echo "$lines" ## multiple lines
echo $lines ## all spaces (including newlines) are zapped