sh スクリプトでこの構成をよく見かけます。
if [ "z$x" = z ]; then echo x is empty; fi
なぜ彼らはこのように書かないのですか?
if [ "$x" = "" ]; then echo x is empty; fi
TL;DR 短い答え
この構成では:
if [ "z$x" = z ]; then echo x is empty; fi
これは、面白いコンテンツやその他の多くの問題z
に対するガードです。$x
なしで書くとz
:
if [ "$x" = "" ]; then echo x is empty; fi
取得$x
する文字列が含まれています。-x
if [ "-x" = "" ]; then echo x is empty; fi
そして、それは のいくつかの古い実装を混乱させ[
ます。
さらに引用符を省略して文字列を含める$x
と、次のようになります。$x
-f foo -o x
if [ -f foo -o x = "" ]; then echo x is empty; fi
そして今、それはまったく違うものを静かにチェックします。
ガードは、これらのおそらく正直な人為的エラー、おそらく悪意のある攻撃が静かに失敗するのを防ぎます。ガードを使用すると、正しい結果またはエラー メッセージが表示されます。詳細な説明を読んでください。
丁寧な説明
z
イン_
if [ "z$x" = z ]; then echo x is empty; fi
警備員と呼ばれます。
ガードが必要な理由を説明するために、まず bash conditional の構文を説明したいと思いますif
。[
は構文の一部ではないことを理解することが重要です。コマンドです。test
コマンドのエイリアスです。そして、現在のほとんどのシェルでは、組み込みコマンドです。
の文法規則if
は、おおよそ次のとおりです。
if command; then morecommands; else evenmorecommands; fi
(else
パーツはオプションです)
command
任意のコマンドにすることができます。本当にどんなコマンドでも。に遭遇したときにbashが行うことは、if
おおよそ次のとおりです。
command
ます。command
。0
実行しmorecommands
ます。終了ステータスがそれ以外で、else
パーツが存在する場合は、 を実行しevenmorecommands
ます。それを試してみましょう:
$ if true; then echo yay; else echo boo; fi
yay
$ if wat; then echo yay; else echo boo; fi
bash: wat: command not found
boo
$ if echo foo; then echo yay; else echo boo; fi
foo
yay
$ if cat foo; then echo yay; else echo boo; fi
cat: foo: No such file or directory
boo
test
コマンドを試してみましょう:
$ if test z = z; then echo yay; else echo boo; fi
yay
そしてエイリアス[
:
$ if [ z = z ]; then echo yay; else echo boo; fi
yay
[
構文の一部ではありません。それはただの命令です。
z
here には特別な意味はありません。それは単なる文字列です。
[
の外でコマンドを試してみましょうif
:
$ [ z = z ]
何も起こりません?終了ステータスを返しました。で終了ステータスを確認できますecho $?
。
$ [ z = z ]
$ echo $?
0
等しくない文字列を試してみましょう:
$ [ z = x ]
$ echo $?
1
はコマンドであるため[
、他のコマンドと同様にパラメーターを受け入れます。実際、クロージング]
もパラメータであり、最後に来なければならない必須パラメータです。欠落している場合、コマンドは文句を言います:
$ [ z = z
bash: [: missing `]'
bash が不平を言うのは誤解を招きます。実際には、組み込みコマンド[
が文句を言います。システムを呼び出すと、誰が不平を言っているのかがより明確にわかります[
。
$ /usr/bin/[ z = z
/usr/bin/[: missing `]'
興味深いことに、システム[
は常に終了を要求するとは限りません]
。
$ /usr/bin/[ --version
[ (GNU coreutils) 7.4
...
閉じる前にスペースが必要です。]
そうしないと、パラメーターとして認識されません。
$ [ z = z]
bash: [: missing `]'
[
そうしないと、bash が別のコマンドを実行したいと考えるようになるため、その後にもスペースが必要です。
$ [z = z]
bash: [z: command not found
を使用すると、これはより明白になりますtest
。
$ testz = z
bash: testz: command not found
Remember[
は の別名ですtest
。
[
文字列を比較するだけではありません。数値を比較できます。
$ [ 1 -eq 1 ]
$ [ 42 -gt 0 ]
ファイルまたはディレクトリの存在を確認することもできます。
$ [ -f filename ]
$ [ -d dirname ]
(または)の機能の詳細については、help [
またはを参照してください。システムコマンドのドキュメントが表示されます。bash 組み込みコマンドのドキュメントが表示されます。man [
[
test
man
help
基礎を説明したので、あなたの質問に答えることができます:
なぜ人々はこれを書くのですか:
if [ "z$x" = z ]; then echo x is empty; fi
これではありません:
if [ "$x" = "" ]; then echo x is empty; fi
簡潔にするif
ために、これは約[
.
このz
構造の :
[ "z$x" = z ]
$x
は、の古い実装と組み合わせたのおかしなコンテンツに対するガード[
、および/または の引用を忘れるなどの人為的エラーに対するガード$x
です。
$x
のような面白いコンテンツがあるとどうなり-f
ますか?
これ
[ "$x" = "" ]
となります
[ "-f" = "" ]
の一部の古い実装で[
は、最初のパラメーターが で始まると混乱します-
。は、 の内容に関係なくz
、最初のパラメータが で始まらないようにします。-
$x
[ "z$x" = "z" ]
となります
[ "z-f" = "z" ]
引用するのを忘れたらどうなります$x
か?のような面白いコンテンツ-f foo -o x
は、テストの全体的な意味を変える可能性があります。
[ $x = "" ]
となります
[ -f foo -o x = "" ]
テストは現在、ファイル foo の存在をチェックしてから、論理的またはx
空の文字列であるかどうかをチェックしています。最悪の部分は、エラー メッセージがなく、終了ステータスしか表示されないため、気付かないことです。$x
ユーザー入力によるものである場合、これは悪意のある攻撃にも使用できます。
お守りと一緒にz
[ z$x = z ]
となります
[ z-f foo -o x = z ]
少なくとも、次のエラー メッセージが表示されます。
$ [ z-f foo -o x = z ]; echo $?
bash: [: too many arguments
ガードは、空の文字列ではなく未定義の変数の場合にも役立ちます。一部の古いシェルでは、未定義の変数と空の文字列に対して異なる動作がありました。この問題は基本的に解決されています。これは、最新のシェルでは undefined がほとんど空の文字列のように動作するためです。
概要:
引用符は$x
、未定義のケースを空の文字列のケースのように動作させるのに役立ちます。
前のガード$x
は、上記の他のすべての問題をさらに防ぐのに役立ちます。
ガード前$x
は、これらの可能性のあるすべてのエラーを防ぎます。
$x
(悪意のあるユーザーによるコード インジェクション)の面白いコンテンツ[
(文字列が で始まると混乱する-
)$x
可能性があります)-f foo -o x
$x
。(未定義の場合、古い実装では動作が異なります)ガードは正しいことを行うか、エラー メッセージを表示します。
の最新の実装で[
はいくつかの問題が修正され、最新のシェルには他のケースに対するいくつかの解決策がありますが、それらには独自の落とし穴があります。z
他の点に気をつけていれば、ガーディングは必要ありませんが、単純なテストを書く際のミスを避けるのがはるかに簡単になります。
以下も参照してください。
ゼロの長さをテストするには、次を使用します-z
。
if [ -z "$x" ] ; then
echo x is empty
fi
bash では、[[
引用符を必要としないものを使用できます。
if [[ -z $x ]] ; then
echo x is empty
fi
man 1p sh
POSIXシェルのドキュメントで次のことを見つけました。
歴史的なシステムも、共通の構造を考えると信頼できませんでした。
test "$response" = "expected string"
次のいずれかがより信頼性の高い形式です。
test "X$response" = "Xexpected string" test "expected string" = "$response"
2 番目の形式は、予想される文字列が単項プライマリと混同されないことを前提としていることに注意してください。予想される文字列が「-」、「(」、「!」、さらには「=」で始まる場合は、代わりに最初の形式を使用する必要があります。
短くて簡単な答え:[
実際には bash ディレクティブではありませんが、そう[[
です。代わりに、コマンド ライン ユーティリティへのシンボリック リンクですtest
。
理由は次のとおりです。
他のコマンド ライン ユーティリティと同様test
に、a で始まるものはすべて-
オプションとして解釈されます。また、で始まるものはすべて=
演算子と見なされます。引数の前に[
(またはtest
) の前に英字を付けない場合、テストが確実に機能するという保証はありません。
次の値を検討してください。
a=1
b=1
そして評価:
[ "$a" = "$b" ] && echo "Yes, they match"
]
基本的に次のコマンドを実行しています (実行名が の場合、テストは終了を無視します[
):
test 1 = 1 ] && echo "Yes, they match"
ここで、次の値を検討してください。
a="-lt"
b="-lt"
引数-lt
はテストするオプションです。したがって、同じテストを実行すると、次のように展開されます。
test -lt = -lt ] && echo "Yes, they match"
=
or!=
演算子の前後のオプションを無視するように test が書き直されているため、これは Linux システム (または少なくとも最近のシステム) では問題ありません。ただし、一部の古い UNIX システムでは、次のようなエラーで中断します。
test: -lt: unary operator expected
x が定義されていることを確認したい場合:
if [ ${x:-Z} = 'Z' ];then
echo x is empty
fi