誰かがシェルスクリプトで変数を引用符で囲む必要があるかどうかを教えてもらえますか?
たとえば、次は正しいですか。
xdg-open $URL
[ $? -eq 2 ]
または
xdg-open "$URL"
[ "$?" -eq "2" ]
もしそうなら、なぜですか?
誰かがシェルスクリプトで変数を引用符で囲む必要があるかどうかを教えてもらえますか?
たとえば、次は正しいですか。
xdg-open $URL
[ $? -eq 2 ]
または
xdg-open "$URL"
[ "$?" -eq "2" ]
もしそうなら、なぜですか?
回答:
一般的な規則:空であるか、スペース(または実際には空白)または特殊文字(ワイルドカード)を含むことができる場合は、引用符で囲みます。スペースで文字列を引用しないと、多くの場合、シェルが単一の引数を複数に分解します。
$?
数値なので、引用符は必要ありません。$URL
それが必要かどうかは、そこで許可するものと、それが空の場合でも引数が必要かどうかによって異なります。
なぜなら、その方がより安全だからです。
IFS=0
、それecho $?
は非常に驚くべきことです。
cp $source1 $source2 $dest
、を書くことができると思いますが、予期dest
しない理由で設定されない場合、3番目の引数は表示さsource1
れsource2
ず、暗黙的にコピーされます。空白の宛先に対する適切なエラー(各引数を引用符で囲んだ場合と同様)。
quote it if...
思考プロセスは逆です。引用符は必要なときに追加するものではなく、必要なときに削除するものです。二重引用符を使用する必要がある場合(例:変数を展開する場合)または引用符を使用する必要がない場合(例:グロビングとファイル名の展開を行う場合)を除き、文字列とスクリプトは常に一重引用符で囲みます。
要するに、トークン分割とワイルドカード拡張を実行するためにシェルを必要としないすべてのものを引用します。
一重引用符は、それらの間のテキストを逐語的に保護します。シェルが文字列にまったく触れないようにする必要がある場合、これは適切なツールです。通常、これは、変数補間が必要ない場合に選択する引用メカニズムです。
$ 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
。ただし、ワイルドカード一致でのループは一般的な問題であり、頻繁に正しく行われません。)
ループするトークンのリストや展開するワイルドカードを含む変数はあまり見られないため、「何をしているのか正確に知らない限り、すべてを引用する」と省略されることがあります。
一般的な引用の3点式は次のとおりです。
二重引用符
単語の分割とグロビングを抑制したい場合。また、リテラルを正規表現ではなく文字列として処理する必要があるコンテキストでも使用できます。
単一引用符
文字列リテラルで、バックスラッシュの補間と特別な処理を抑制したい場合。つまり、二重引用符を使用することが不適切な状況です。
引用なし
単語の分割またはグロビングの問題がないことが確実であるか、単語の分割およびグロビングが必要な場合。
例
二重引用符
"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}"
[[ ]]
単語の分割やグロビングの問題のない内部表現(これはスタイルの問題であり、意見は大きく異なる可能性があります)for word in $words
)for txtfile in *.txt; do ...
)~
として解釈したい場所$HOME
~/"some dir"
"~/some dir"
以下も参照してください。
"ls" "/"
「すべての文字列コンテキスト」というフレーズは、より慎重に修飾する必要があります。
[[ ]]
、引用符は=
/ ==
およびの右側で問題になり=~
ます。これは、文字列をパターン/正規表現として解釈するか、文字どおりに解釈するかによって違いがあります。
$'...'
)には必ず独自のセクションがあります。
"ls" "/"
、より一般的なの代わりに常に入力する必要があることを示してls /
おり、私はそれをガイドラインの大きな欠陥として捉えています。
case
:)
スペースが含まれ"$var"
て$var
いないことが確実でない限り、私は通常、安全のためにquoted like を使用しています。
$var
行を結合する簡単な方法として使用します。
lines="`cat multi-lines-text-file.txt`"
echo "$lines" ## multiple lines
echo $lines ## all spaces (including newlines) are zapped