Bash 変数では、$myvar と "$myvar" の違いは何ですか? (特定の奇妙な動作)

Bash 変数では、$myvar と "$myvar" の違いは何ですか? (特定の奇妙な動作)

一般的な質問:

Bash では、変数を使用するにはmyvar次の 2 つの方法があることがわかります。

# Define a variable:
bash$ myvar="two words"

# Method one to dereference:
bash$ echo $myvar
two words

# Method two to dereference:
bash$ echo "$myvar"
two words

上記の場合、動作は同じです。これは、 のecho動作によるものです。他の Unix ユーティリティでは、単語が二重引用符でグループ化されているかどうかによって大きな違いが生じます。

bash$ myfile="Cool Song.mp3"
bash$ rm "$myfile"            # Deletes "Cool Song.mp3".
bash$ rm $myfile              # Tries to delete "Cool" and "Song.mp3".

この違いのより深い意味は何なのか疑問に思っています。最も重要なのは、コマンドに渡される内容を正確に確認して、適切に引用されているかどうかを確認するにはどうすればよいかということです。

具体的な奇妙な例:

観察された動作に基づいてコードを記述します。

bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate"    # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.

なぜ二重引用符が必要なのでしょうか? 変数が二重引用符なしで逆参照された後、git-log は正確に何を見ているのでしょうか?

しかし、これを見てください:

bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace        # Now THIS works great.
bash$ git log "$nospace"      # This kind of works, here is a snippet:

# From git-log output:
Date:   "2018-04-12"

うわあ、なぜ印刷された出力に二重引用符が含まれているのでしょう? 二重引用符が不要な場合は削除されず、不要な場合にのみリテラルの引用符文字として解釈されるようです。

Git に引数として渡されるものは何ですか?どうやって調べたらいいのか知りたいです。

さらに複雑なことに、私は を使用してargparseすべての引数をプリントするだけの Python スクリプトを書きました (Bash が解釈したとおりに、つまり、Bash が引数の一部であると考える二重引用符リテラルや、Bash が適切と判断するグループ化または非グループ化された単語)。この Pythonargparseスクリプトは非常に合理的に動作します。残念ながら、これはargparseBash の既知の問題を黙って修正し、Bash が渡しているめちゃくちゃな内容を隠しているのではないかと思います。これは単なる推測で、私にはわかりません。おそらく、git-log は Bash が渡す内容を密かに台無しにしているのでしょう。

あるいは、何が起こっているのか全く分からないだけなのかもしれません。

ありがとう。

編集済み 編集:答えが出る前に言っておきます。私はできるとわかっています多分全体を一重引用符で囲み、二重引用符をエスケープしないでください。これは、git-log を使用した当初の問題に対しては実際にいくらかうまく機能しますが、他のコンテキストでテストしたところ、ほぼ同じくらい予測不可能で信頼性に欠けることがわかりました。変数内の引用符に何か奇妙なことが起こっています。一重引用符で発生した奇妙なことすべてを投稿するつもりはありません。

編集2 - これも機能しません:素晴らしいアイデアが浮かんだのですが、まったくうまくいきません。

bash$ mydate="--date=format:%Y-%m-%d\ T%H"
bash$ git log "$mydate"

# Git log output has this:
Date:   2018-04-12\ T23

引用符で囲まれていないので、しかし日付文字列にリテラルのバックスラッシュ文字が含まれています。また、git log $mydate引用符がないため、変数にバックスラッシュとスペースが含まれているため、エラーが発生します。

答え1

異なるアプローチ:

を実行するとgit log --format="foo bar"、これらの引用符は git によって解釈されず、シェルによって削除されます (引用符で囲まれたテキストが分割されるのを防ぎます)。その結果、引数は 1 つになります。

  • --format=foo bar

しかし、引用符で囲まれていない場合変数展開すると、結果は単語分割されますが、ない引用符で囲まないことで、変数に が含まれている場合--format="foo bar"、次の引数に展開されます。

  • --format="foo
  • bar"

これは以下を使用して確認できます:

  • printf '%s\n' $変数

...また、受け取った引数を出力する単純なスクリプトも同様です。

  • #!/usr/bin/env パール
    $i (0..$#ARGV) の場合 {
        print ($i+1)." = ".$ARGV[$i]."\n";
    }
    
  • #!/usr/bin/env python3
    インポートシステム
    i、arg を enumerate(sys.argv) で指定します:
        print(i, "=", arg)
    

常にbashが利用できる場合は、回避策として以下を使用することをお勧めします。配列変数:

myvar=( --format="foo bar" )

これにより、通常の解析は展開時ではなく割り当て時に実行されます。この構文を使用して変数の内容を展開し、各要素に独自の引数を取得します。

git log "${myvar[@]}"

答え2

元のコマンドが機能しないのはなぜですか?

bash$ mydate="--date=format:\"%Y-%m-%d T%H\""
bash$ git log "$mydate"    # This works great.
bash$ git log $mydate
fatal: ambiguous argument 'T%H"': unknown revision or path not in the working tree.

あなたが尋ねる:

なぜ二重引用符が必要なのでしょうか? 変数が二重引用符なしで逆参照された後、git-log は正確に何を見ているのでしょうか?

を二重引用符で囲まない場合$mydate、変数はそのまま展開され、実行される前のシェル行は次のようになります。

git log --date=format:"%Y-%m-%d T%H"
                      ^————————————^—————— literal quotes

\"ここでは、変数の割り当てでを使用して、(不必要に)リテラル引用符を追加しました。

コマンドは単語の分割は、、およびのgit3 つの引数を受け取るため、 という名前のコミットまたはオブジェクトが見つからないというエラーが発生します。log--date-format:"%Y-%m%-dT%H"T%H"


正しいアプローチは何でしょうか?

引数をまとめておきたい場合、その引数に空白が含まれているときは、引数を引用符で囲む必要があります。一般的に、変数は常に二重引用符で囲みます。

これは、変数内にスペースがあっても機能します。

mydate="--date=format:%Y-%m-%d T%H"
git log "$mydate"

これで、 の 3 番目の引数は、最初に指定したスペースを含めて にgitなります。 に渡される前に、すべての引用符はシェルによって削除されます。$mydategit

追加の引用符は必要ありません。1gitつの引数だけを確認したい場合は、変数を渡すときにその引数を引用符で囲みます"$mydate"


また、次のように質問します。

bash$ nospace="--date=format:\"%Y-%m-%d\""
bash$ git log $nospace        # Now THIS works great.
bash$ git log "$nospace"      # This kind of works, here is a snippet:

# From git-log output:
Date:   "2018-04-12"

あなたの質問:

うわあ、なぜ印刷された出力に二重引用符が含まれるようになったのでしょうか?

なぜなら、あなたは再びリテラル引数に引用符 (エスケープすることによって) を使用します。実際のコマンドで変数を引用符で囲むのを忘れると、引用符は「実際の」引用符に変換されます。「忘れる」と言うのは、シェル コマンドで引用符で囲まれていない変数を使用すると、通常は問題が発生するだけだからです。ここでは、最初に変数を指定したときに発生したエラーが元に戻っています。

追記: 混乱させてしまうかもしれませんが、これが Bash であり、明確なルールに従っています。バグではありません。関連エッセイシェルでのファイル名に関する記事も、Bash での空白処理の問題に触れているため、非常に興味深い内容です。

関連情報