對於 Bash 變量,$myvar 和「$myvar」有什麼區別? (具體奇怪的行為)

對於 Bash 變量,$myvar 和「$myvar」有什麼區別? (具體奇怪的行為)

一般問題:

myvar在 Bash 中,我知道可以透過兩種方式使用變數:

# 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 作為參數傳遞的是什麼?我希望我知道如何找到答案。

為了讓事情變得更複雜,我編寫了一個 Python 腳本,argparse它只列印所有參數(正如Bash 解釋的那樣,因此使用雙引號文字,Bash 認為它們是參數的一部分,並且將單字分組或不分組為Bash )認為合適),並且 Pythonargparse腳本的行為非常合理。可悲的是,我認為argparse可能會默默地修復 Bash 的一個已知問題,從而掩蓋 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 解釋——它們會被 shell 刪除(並保護引用的文字不會分割)。這會產生一個參數:

  • --format=foo bar

然而,當不加引號時變數被擴展,結果經過分詞,但是不是透過取消引用。因此,如果您的變數包含--format="foo bar",它將擴展為以下參數:

  • --format="foo
  • bar"

這可以使用以下方法進行驗證:

  • printf '%s\n' $變量

...以及列印其收到的參數的任何簡單腳本。

  • #!/usr/bin/env perl
    對於 $i (0..$#ARGV) {
        印 ($i+1)." = ".$ARGV[$i]."\n";
    }
    
  • #!/usr/bin/env python3
    導入系統
    對於 i,枚舉中的 arg(sys.argv):
        印(我,“=”,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,則該變數將逐字展開,並且 shell 行在執行之前將如下所示:

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

在這裡,您(不必要地)透過\"在變數賦值中添加了文字引號。

由於該命令將經過分詞git將收到三個參數,log--date-format:"%Y-%m%-dT%H",因此抱怨找不到任何名為 的提交或物件T%H"


正確的做法是什麼?

如果要將參數保留在一起,且該參數包含空格,則必須將參數以引號引起來。通常,始終將變數括在雙引號中。

即使變數內部有空格,這也有效:

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

現在,第三個參數將為git$mydate包括您最初指定的空格。所有引號在傳遞給 之前都會被 shell 刪除git

您根本不需要額外的引號 - 如果您只想git查看一個參數,請在傳遞變數時將該參數括在引號中"$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"

你的問題:

糟糕,為什麼現在列印的輸出中有雙引號?

因為你又包含了文字參數中的引號(透過轉義它們),當您忘記在實際命令中引用變數時,它們會變成「真正的」引號。我說「忘記」是因為在 shell 命令中使用不含引號的變數通常只會給您帶來麻煩,而這裡它是在逆轉您在首先指定變數時所犯的錯誤。

PS:我知道這很令人困惑,但這就是 Bash,它遵循一些明確的規則。這裡沒有錯誤。 A相關論文關於 shell 中的檔案名稱也非常有啟發性,因為它涉及 Bash 中的空白處理問題。

相關內容