假設我有以下兩個 shell 腳本:
#!/bin/sh
#This script is named: args.sh
echo 1 "\"Two words\"" 3
, 和:
#!/bin/sh
#This script is named: test.sh
echo "Argument 1: "$1
echo "Argument 2: "$2
echo "Argument 3: "$3
當我將腳本稱為:
sh test.sh $(sh args.sh)
,我收到:
Argument 1: 1
Argument 2: "Two
Argument 3: words"
當我期望得到:
Argument 1: 1
Argument 2: Two words
Argument 3: 3
複製 的輸出sh args.sh
並將其貼上為 的輸入sh test.sh
就可以了;所以我認為這實際上並不是 shell 正在做的事情。我可以透過呼叫來實現所需/預期的輸出sh args.sh | xargs sh test.sh
。
但是,我想知道是否有一種等效的方法可以在不將第一個腳本(args.sh)的輸出通過管道傳輸到 xargs 的情況下執行此操作?我希望腳本按原始順序調用;參數腳本將參數輸出到第二個。我也在尋找為什麼此調用不能按預期工作的解釋?
答案1
部分問題是 args.sh 傳回的字串沒有像直接命令一樣進行解析,而是僅透過 $IFS ( $' \t\n'
) 的值進行解析。嘗試使用以下命令開啟命令追蹤set -x
:
$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh 1 '"Two' 'words"' 3
Argument 1: 1
Argument 2: "Two
Argument 3: words"
$
請注意以單一 開頭的行+
:有四個參數,其中'"Two'
和'words"'
兩個解析為單獨的參數。您想要做的是更改 $IFS。
$ set -x
$ IFS='"'
+ IFS='"'
$ sh /tmp/test.sh $(sh /tmp/args.sh)
++ sh /tmp/args.sh
+ sh /tmp/test.sh '1 ' 'Two words' ' 3'
Argument 1: 1
Argument 2: Two words
Argument 3: 3
$
這不適用於每個輸出。最好的方法是更改 args.sh 的輸出,以使用不同於空格的內容分隔輸出,例如逗號或冒號:
$ cat /tmp/args.sh
#!/bin/sh
#This script is named: args.sh
echo "1,Two words,3"
$ IFS=,
$ sh /tmp/test.sh $(sh /tmp/args.sh)
+ sh /tmp/args.sh
+ sh /tmp/test.sh 1 Two words 3
Argument 1: 1
Argument 2: Two words
Argument 3: 3
$
答案2
當變數替換$var
或命令替換$(cmd)
不加引號時,結果將發生以下轉換:
請注意,結果不是字串,而是字串列表。此外,請注意,"
此處不涉及引號字元;它們是 shell 來源語法的一部分,而不是字串擴充的一部分。
shell 程式設計的一般規則是始終在變數和命令替換兩邊加上雙引號,除非您知道為什麼必須將它們去掉。所以在 中test.sh
,寫echo "Argument 1: $1"
.要將參數傳遞給test.sh
,您遇到了麻煩:您需要將單字清單從 傳遞args.sh
給test.sh
,但您選擇的方法涉及命令替換,並且僅提供了簡單字串的方式。
如果你能保證傳遞的參數不包含任何換行符,而呼叫過程稍作改變也是可以接受的,那麼你可以設定IFS
為只包含換行符。確保args.sh
每行輸出一個檔案名,並且沒有雜亂的引號。
IFS='
'
test.sh $(args.sh)
unset IFS
如果參數可能包含任意字元(大概除了空字節,不能將其作為參數傳遞),則需要執行一些編碼。任何編碼都可以;當然,這與直接傳遞參數不同:那是不可能的。例如,在(如果您的 shell 不支持,則args.sh
替換為實際的製表符):\t
for x; do
printf '%s_\n' "$x" |
sed -e 's/q/qq/g' -e 's/ /qs/g' -e 's/\t/qt/g' -e 's/$/qn/'
done
並在test.sh
:
for arg; do
decoded=$(printf '%s\n' "$arg" |
sed -e 's/qs/ /g' -e 's/qt/\t/g' -e 's/qn/\n/g' -e 's/qq/q/g')
decoded=${decoded%_qn}
# process "$decoded"
done
您可能更願意更改test.sh
為接受字串列表作為輸入。如果字串不包含換行符,args.sh | test.sh
則在args.sh
(解釋):
while IFS= read -r line; do
# process "$line"
done
另一種完全不需要引用的方法是從第一個腳本呼叫第二個腳本。
…
args.sh "$@"