Bash

請使用 IFS 解釋這些參數擴展的行為?

  • November 18, 2021

我試圖弄清楚如何使用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 中的工作方式相同。

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