假设我有以下两个 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 "$@"