Bash

為什麼在執行命令之前,bash 會在命令中未加引號的失敗路徑名擴展中添加單引號?

  • November 28, 2021

我正在探索使用set -x( set +xto unset) in 來跟踪命令bash

列印簡單命令的踪跡,用於命令、case 命令、選擇命令以及命令及其參數或相關單詞列表的算術運算(在它們展開之後和執行之前)。PS4 變數的值被擴展,結果值在命令及其擴展參數之前列印。

現在考慮以下內容,跟踪帶引號和不帶echo \[-neE\] \[arg …\]引號的 bash 內置命令的使用:

# set -x        # what I typed
# echo 'love'   # ...
+ echo love     <--(1) the trace
love            # the output

# echo love?    # note the input contains no quote whatsoever
+ echo 'love?'  <--(2) note the trace contains quotes after returning word
love?           # i.e. failed to find any file 

# echo 'love?'  # note the input contains single quotes
+ echo 'love?'  <--(3) traces like (2)
love?

# touch loveu   # we create a file that matches the love? pattern
+ touch loveu

# echo love?    # of course now, the pattern matches the created file now
+ echo loveu    <--(4) indeed it finds it and expands to name
loveu           # the name is echoed

所以?在這種情況下確實被解釋為用於模式匹配**路徑名擴展中的單個字元的特殊字元。果然,一旦在目前目錄中創建了與該模式匹配的文件,就會發生匹配並列印文件名。當然,這種行為已記錄在案

如果沒有找到匹配的文件名,並且 shell 選項 nullglob 被禁用,則單詞保持不變。

但問題是 (2) 中的單詞未引用love?not 'love?'。跟踪顯示命令執行之前但擴展之後的狀態,正如我們所見,路徑名擴展是因為?在第一種情況(2)中沒有匹配項,我們使用了特殊字元。所以單引號出現在那種情況下,就像我們自己使用單引號(3)一樣?而在其他情況下,要麼找到文字,要麼找到匹配項,並相應地“替換”了命令中的模式。這似乎是擴展後立即刪除引號的手冊部分中的含義:

在前面的擴展之後,所有未加引號的字元 ‘'、’’’ 和 ‘"’都不是由上述擴展之一產生的。(我的斜體)

所以在這裡(2)我們有未引用的事件,'這些事件是由先前的擴展引起的。我沒有把它們放在那裡;bash 做到了,現在它們沒有被刪除- 我們就在執行命令之前。


類似的插圖for

for name [ [in [words …] ] ; ] do commands; done考慮在循環1中使用的這個列表,沒有匹配的文件:

# for i in love love? 'love?'; do echo $i; done
+ for i in love 'love?' ''\''love?'\'''
+ echo love
love
+ for i in love 'love?' ''\''love?'\'''
+ echo 'love?'
love?
+ for i in love 'love?' ''\''love?'\'''
+ echo 'love?'
love?

所以echo命令的行為是完全一樣的,但在for結構中的項目的情況下,它似乎試圖……引用我的引號來逃避自己?我不確定…


問題

  • single quotes為什麼在 context(2)中用未引用的失敗路徑名擴展模式表示;無論如何擴展都完成了,我們要執行?同樣,我們已經完成了擴展,但模式失敗了——不再需要擴展。我想我要問的是我們為什麼要在這一點上關心-我們所處的位置就在 bash 手冊中的 3.7.2-4 之前。為什麼這不是“按原樣”保留的,而擴展只是為了執行命令而關閉,例如set -f
  • for循環對列表中的單引號項目做了什麼?)

  1. 當使用這種帶有 for 的單詞列表結構時,它實際上是一個項目列表,並且值是為了方便起見,我覺得 t="0"; for i in 0 0 0 0; do let t++; echo "yes, this is really $t times"; done 非常有說服力。

當被指示在執行命令時回顯命令(“執行跟踪”)時,bash使用ksh幾種元字元1之一在任何單詞周圍添加單引號。

元字元可以通過多種方式進入這個詞。單詞(或其中的一部分)可以用單引號或雙引號引起來,字元可以用 轉義\,或者由於文件名匹配嘗試失敗而保留下來。在所有情況下,執行跟踪都將包含單引號單詞,例如:

$ set -x
$ echo foo\;bar
+ echo 'foo;bar'

**這只是 shell 實現執行跟踪方式的產物;它不會改變參數最終傳遞給命令的方式。**引號被添加、列印和丟棄。這是bash原始碼的相關部分,print_cmd.c

/* A function to print the words of a simple command when set -x is on. */
void
xtrace_print_word_list (list, xtflags)
...
{
 ...
 for (w = list; w; w = w->next)
   {
     t = w->word->word;
     ...
     else if (sh_contains_shell_metas (t))
       {
         x = sh_single_quote (t);
         fprintf (xtrace_fp, "%s%s", x, w->next ? " " : "");
         free (x);
       }

至於作者為什麼選擇這樣做,那裡的程式碼沒有說。但是這裡有一些類似的程式碼variables.c,並且帶有註釋:

/* Print the value cell of VAR, a shell variable.  Do not print
  the name, nor leading/trailing newline.  If QUOTE is non-zero,
  and the value contains shell metacharacters, quote the value
  in such a way that it can be read back in. */
void
print_var_value (var, quote)
...
{
 ...
 else if (quote && sh_contains_shell_metas (value_cell (var)))
   {
     t = sh_single_quote (value_cell (var));
     printf ("%s", t);
     free (t);
   }

所以可能這樣做是為了更容易從執行跟踪的輸出中複製命令行並再次執行它們。


1將被引用的字元有:IFS 空格(空格、製表符、換行符)、引號字元(單引號和雙引號、反斜杠)、shell 元字元(|&;()<>)、保留字(!{})、萬用字元(*, [, ?, ], ^),擴展字元($, 反引號),可能是波浪號,可能是#. 看接近尾聲lib/sh/shquote.c

引用自:https://unix.stackexchange.com/questions/118403