題 使用Bash變量,$ myvar和“$ myvar”之間有什麼區別? (具體的奇怪行為)


一般問題:

在Bash中,我知道使用變量 myvar 可以通過兩種方式完成:

# 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認為合適的單詞)和Python argparse 腳本表現得非常合理。可悲的是,我想 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 沒有引號錯誤,變量中帶有反斜杠空格。


2
2018-04-13 06:34


起源


這個Q只關於git嗎?還是空白? - Xen2050
@ Xen2050我老實說不確定問題是否與Git有關。我相當肯定它與Bash有關。 Git可能已經破壞了某些東西,或者Python的argparse已經修復了一些東西,因為它們有不同的行為。此外,我真正想要的值包含 - ,=,:,[空格],%,還有雙引號或單引號,因此它可能是一個非常難以使用的值。 - SerMetAla


答案:


不同的方法:

當你跑步 git log --format="foo bar",那些引號不是由git解釋的 - 它們被shell刪除(並保護引用的文本不被拆分)。這導致一個arg:

  • --format=foo bar

但是,當沒有引用時 變量 擴展,結果通過分詞,但是  通過不引用。所以如果你的變量包含 --format="foo bar",它擴展到這些args:

  • --format="foo
  • bar"

這可以通過以下方式驗證:

  • printf'%s \ n'$ variable

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

  • #!/ usr / bin / env perl
    for $ i(0 .. $#ARGV){
        print($ i + 1)。“=”。$ ARGV [$ i]。“\ n”;
    }
    
  • #!/ usr / bin / env python3
    導入系統
    for i,arg in enumerate(sys.argv):
        print(i,“=”,arg)
    

如果您始終使用bash,則首選的解決方法是使用 排列 變量:

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

有了這個,通常的解析是在賦值期間完成的,而不是在擴展期間完成的。您可以使用此語法擴展變量的內容,每個元素都有自己的arg:

git log "${myvar[@]}"

4
2018-04-13 07:21



我接受了這個答案,它比另一個答案更有幫助。謝謝。 - SerMetAla
請在最頂層添加,因為這是答案的關鍵: mydate="--date=format:%Y-%m-%d T%H" 這不使用數組變量(非常棒),它強調了只更改原始問題代碼中的一個字符的工作解決方案。謝謝。 - SerMetAla
@SerMetAla我只是好奇:簡單地使用我展示的方法,即具體問題是什麼,即 mydate="--date=format:%Y-%m-%d T%H" 和 git log "$mydate"? - slhck
@slhck我認為理想的答案是這個答案,我已經接受了,再加上答案中的一個片段,理想情況下是在這個答案的頂部。我實際上會使用你的答案中的那個片段,謝謝。這個答案包含瞭如何使用其中任何一個來查看git-log實際看到的內容的正確解釋 printf 在Bash本身(我測試過)或Python(我測試過)或Perl(我沒有測試,我通過外推信任它)。這個答案還強調了你的實用代碼片段中實際發生的事情:Bash是 加入 它需要的雙引號,所以我不應該輸入它。 - SerMetAla
我相信我已經在“使用數組”中回答了這個問題。 - grawity


為什麼你的原始命令不起作用?

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%-d 和 T%H"因此抱怨沒有找到任何提交或對象命名 T%H"


什麼是正確的方法?

如果要將參數保持在一起,如果該參數包含空格,則必須將參數包裝在引號中。 通常,始終用雙引號包裝變量。

即使變量中有空格,這也可以工作:

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

現在是第三個參數 git 將會 $mydate,包括您最初指定的空間。在傳遞給shell之前,所有引號都被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,它遵循一些明確的規則。這裡沒有錯誤。一個 相關論文 關於shell中的文件名也很有啟發性,因為它觸及了Bash中的空白處理問題。


2
2018-04-13 07:15



你說了一些真實的東西,但是,下面的命令 確實有效 這就是我想要的: git log --date=format:"%Y-%m-%d T%H" 你說它好像不起作用,但確實有效。此外,沒有其他方法可以使它工作。 - SerMetAla
是的,當然直接輸入時該命令有效。當您首先將(日期格式)參數分配給包含文字引號的變量,然後展開該變量時,它才起作用。 - slhck
@SerMetAla語法引用之間的關鍵區別(即 周圍 數據,並且是你想要的)和文字引號(它們是 部分 數據,是你將引號放入變量值後得到的數據。 - Gordon Davisson