請使用 IFS 解釋這些參數擴展的行為?
我試圖弄清楚如何使用and進行
${parameter%word}
擴展。它首先嘗試製作一個腳本來使用ghostscript組合pdf,但我遇到了一些奇怪的參數擴展行為,現在我只是好奇為什麼會發生這種行為。$@``$*
基本上我試圖從每個參數的末尾刪除“.pdf”,然後將它們與任意字元串連接(我正在使用“-”進行測試),然後在結果的末尾添加一個“.pdf” . 例如,預期的行為是
test.sh a.pdf b.pdf c.pdf
->a-b-c.pdf
。這是我正在執行的測試腳本:IFS='-' echo ${*%.pdf}.pdf echo "${*%.pdf}.pdf" a=${*%.pdf}.pdf b="${*%.pdf}.pdf" echo $a echo $b
如果我
bash test.sh a.pdf b.pdf c.pdf
,我得到:a b c.pdf a-b-c.pdf a b c.pdf a b c.pdf
如果我
zsh test.sh a.pdf b.pdf c.pdf
,我得到:a b c.pdf a.pdf-b.pdf-c.pdf a-b-c.pdf a.pdf-b.pdf-c.pdf
我知道 zsh 和 bash 是不同的,所以我不擔心它們為什麼會給出不同的結果。但是,在每種情況下,構造字元串的 4 種方法中只有一種按預期工作(第二種用於 bash,第三種用於 zsh)。
為什麼這些看似相似的構造字元串的嘗試會導致如此不同的結果?任何見解都值得讚賞。謝謝!
如何
${*%word}
等工作取決於外殼。POSIX未指定結果。有兩種主要的似是而非的行為:轉換(前綴或後綴刪除)可以應用於每個單詞,或者應用於連接單詞的結果。在支持數組的 shell 中,很自然地將轉換應用於每個單詞:這就是 bash 和 ksh93 所做的。在不支持數組的 shell 中,很自然地首先加入單詞(這就是 ash/dash 所做的)。例如:# No arrays: $* joined = 'abc abc'; strip off b* → 'a' $ dash -c 'echo ${*%%b*}' _ abc abc a # Arrays: $* = ('abc' 'abc'); strip off b* from each element → ('a' 'a'); then join $ bash -c 'echo ${*%%b*}' _ abc abc a a
第一個字元
IFS
用於連接組成的單詞$*
。如果模式可以匹配該字元,它只會對剝離的內容產生影響。例如:# No arrays: $* joined = 'abc-def-ghi'; strip off -* → 'abc' $ dash -c 'IFS=-; echo "${*%%-*}"' _ abc-def ghi abc # Arrays: $* = ('abc-def' 'ghi'); strip off -* from each element → ('abc' 'ghi'); then join $ bash -c 'IFS=-; echo "${*%%-*}"' _ abc-def ghi abc-ghi
當替換是在單詞上下文中時,擴展在此結束。單詞上下文包括雙引號和賦值;請參閱何時需要雙引號?以及外殼變數的擴展以及 glob 和 split 對它的影響以獲取更多詳細資訊。這說明
echo "${*%.pdf}.pdf"
: 的第一個字元IFS
用於加入,沒有後續拆分,因此 bash 中的輸出為a-b-c.pdf
. 兩者的價值a
也是b
如此a-b-c.pdf
。當替換在列表上下文中(即未引用)時,如在您的第一個範例中,結果會經歷分詞(和萬用字元)。這是基於 的,
IFS
因此a-b-c.pdf
分為a
和。該命令列印三個單詞,中間有一個空格。完全相同的事情發生在您的範例中:or的值在字元處拆分。b``c.pdf``echo``echo $a``echo $b``a``b``IFS
Zsh 對待
@
和*
不同。作為*
參數名稱,它在雙引號內應用字元串樣式的行為(先連接然後變換),否則應用數組樣式的行為(變換每個元素)。另一方面,參數@
始終被視為一個數組。因此:$ zsh -c 'echo "${*%.pdf}"' _ a.pdf b.pdf c.pdf a.pdf b.pdf c $ zsh -c 'echo ${*%.pdf}' _ a.pdf b.pdf c.pdf a b c $ zsh -c 'echo "${@%.pdf}"' _ a.pdf b.pdf c.pdf a b c $ zsh -c 'echo ${@%.pdf}' _ a.pdf b.pdf c.pdf a b c
與其他 shell 中發生的情況不同,字元串賦值不會導致
$*
以字元串樣式處理:雙引號才是最重要的。這解釋了為什麼a=${*%.pdf}; echo $a
喜歡echo ${*%.pdf}
和不喜歡a="${*%.pdf}"; echo $a
。使用
IFS=-
,在加入時使用破折號*
,無論是由於雙引號還是字元串分配,只要它處於單詞上下文中就會發生這種情況。# ('a.pdf' 'b.pdf' 'c.pdf); strip each element → ('a' 'b' 'c'); print list $ zsh -c 'IFS=-; echo ${*%.pdf}' _ a.pdf b.pdf c.pdf a b c # join → 'a.pdf-b.pdf-c.pdf'; strip the single word and print it $ zsh -c 'IFS=-; echo "${*%.pdf}"' _ a.pdf b.pdf c.pdf a.pdf-b.pdf-c # ('a.pdf' 'b.pdf' 'c.pdf); strip each element → ('a' 'b' 'c'); `$*` in word context so join → 'a-b-c'; print word $ zsh -c 'IFS=-; a=${*%.pdf}; echo "$a"' _ a.pdf b.pdf c.pdf a-b-c # join → 'a.pdf-b.pdf-c.pdf'; strip the single word; print the word $ zsh -c 'IFS=-; a="${*%.pdf}"; echo "$a"' _ a.pdf b.pdf c.pdf a.pdf-b.pdf-c
請注意,您幾乎不應該使用
$*
. 僅將位置參數與 連接起來才有用IFS
,並且無法區分IFS
連接創建的IFS
字元與參數中已經存在的字元。"$@"
幾乎總是正確的形式。請注意,您確實需要雙引號來避免單詞擴展(即使在 zsh 中,儘管在 zsh 中省略引號的效果要小得多)。為了使您的腳本易於理解,請一次執行一個步驟:去掉每個部分的後綴,然後將這些部分連接起來。使用數組變數來儲存中間結果。
parts=("${@%.pdf}") # using @ because we want to have array behavior IFS=- joined="${parts[*]}" # using * and not @ for joining echo "$joined.pdf"
此程式碼段在 bash 和 zsh 中的工作方式相同。