在Bash脚本(没有Perl,Python等等)中应该使用什么成语来为脚本参数中的另一个程序构建命令行,同时正确处理文件名 < / em>的
通过正确,我的意思是处理带有空格或奇数字符的文件名而不会无意中导致其他程序将它们作为单独的参数处理(或者,在<
或{{1的情况下)毕竟,如果正确转义 不幸的 文件名字符,那么它们是有效的 - 做一些更糟糕的事情。)
以下是不正确处理文件名的形式的含义示例:让我们假设这个脚本(>
)构建了一个命令行命令(foo
,假设在路径中)通过获取所有bar
的输入参数并将看起来像标志的任何东西移到前面,然后调用foo
:< / p>
bar
(请注意,此只是一个示例;还有很多其他时间需要执行此操作以及一堆args然后将它们传递到其他程序。)
在简单文件名的天真场景中,效果很好。但是如果我们假设一个包含文件的目录
one two three and a half four < five
然后当然命令#!/bin/bash
# This is clearly wrong
FILES=
FLAGS=
for ARG in "$@"; do
echo "foo: Handling $ARG"
if [ x${ARG:0:1} = "x-" ]; then
# Looks like a flag, add it to the flags string
FLAGS="$FLAGS $ARG"
else
# Looks like a file, add it to the files string
FILES="$FILES $ARG"
fi
done
# Call bar with the flags and files (we don't care that they'll
# have an extra space or two)
CMD="bar $FLAGS $FILES"
echo "Issuing: $CMD"
$CMD
在其任务中失败了:
foo: Handling four < five foo: Handling one foo: Handling three and a half foo: Handling two Issuing: bar four < five one three and a half two
如果我们实际允许foo *
发出该命令,那么结果将不会是我们所期望的。
以前我试过通过确保每个文件名周围都有引号的简单方法来解决这个问题,但我(非常)很快就知道那是不是正确的方法。 : - )
那是什么?约束:
foo
程序和上面的人为例子,而不是使用一个真实的场景,人们可能很容易(并且合理地)沿着尝试的路线前进使用目标程序中的功能。bar
,xargs
或sed
,前提是我们不会太迟钝(请参阅上面的#1) )。 (向Perl,Python等程序员抱歉,他们认为#3和#4相结合以形成任意区别。)提前谢谢,伙计们。
修改:Ignacio Vazquez-Abrams向我指出了Bash常见问题解答entry #50,经过一些阅读和实验后似乎表明一种方法是使用Bash arrays:< / p>
tr
这是正确和合理的吗?或者我依靠上面的环境,以后会咬我。 似乎工作,它为我勾选所有其他框(简单,易记,等等)。它似乎依赖于相对最近的Bash功能(FAQ条目#50提及v3.1,但我不确定这是否是他们使用它的一些语法的一般数组),但我认为我可能只会处理拥有它的版本。
(如果以上是正确的并且您想要取消删除您的答案,Ignacio,我会接受它,前提是我还没有接受任何其他人,尽管我支持我关于仅链接答案的陈述。)
答案 0 :(得分:5)
你为什么要“建立”一个命令?使用适当的方法将文件和标志添加到数组 引用并直接使用带引号的数组作为参数发出命令。
脚本中的选定行(省略未更改的行):
if [[ ${ARG:0:1} == - ]]; then # using a Bash idiom
FLAGS+=("$ARG") # add an element to an array
FILES+=("$ARG")
echo "Issuing: bar \"${FLAGS[@]}\" \"${FILES[@]}\""
bar "${FLAGS[@]}" "${FILES[@]}"
有关以这种方式使用数组的快速演示:
$ a=(aaa 'bbb ccc' ddd); for arg in "${a[@]}"; do echo "..${arg}.."; done
输出:
..aaa..
..bbb ccc..
..ddd..
请参阅BashFAQ/050有关将命令放入变量的信息。您的脚本不起作用的原因是因为无法在带引号的字符串中引用参数。如果你在那里放置引号,它们将被视为字符串本身的一部分而不是分隔符。如果参数不加引号,则完成单词拆分,包含空格的参数被视为多个参数。带有“&lt;”,“&gt;”的参数或“|”在任何情况下都不是问题,因为在变量扩展之前执行重定向和管道,因此它们被视为字符串中的字符。
通过将参数(文件名)放在数组中,保留空格,换行符等。通过引用数组变量作为参数传递时,它们会在前往消费程序的路上保留。
一些补充说明:
答案 1 :(得分:1)
请参阅BashFAQ #50(也可能是关于选项解析的#35)。对于您描述的场景,动态构建命令的地方,最好的选择是使用数组而不是简单的字符串,因为它们不会丢失字边界的位置。一般规则是:创建数组而不是VAR="foo bar baz"
,使用VAR=("foo" "bar" "baz"
);要使用数组而不是$VAR
,请使用"${VAR[@]}"
。以下是使用此方法的示例脚本的工作版本:
#!/bin/bash
# This is clearly wrong
FILES=()
FLAGS=()
for ARG in "$@"; do
echo "foo: Handling $ARG"
if [ x${ARG:0:1} = "x-" ]; then
# Looks like a flag, add it to the flags array
FLAGS=("${FLAGS[@]}" "$ARG") # FLAGS+=("$ARG") would also work in bash 3.1+, as Dennis pointed out
else
# Looks like a file, add it to the files string
FILES=("${FILES[@]}" "$ARG")
fi
done
# Call bar with the flags and files (we don't care that they'll
# have an extra space or two)
CMD=("bar" "${FLAGS[@]}" "${FILES[@]}")
echo "Issuing: ${CMD[*]}"
"${CMD[@]}"
请注意,在echo
命令中,我使用了"${VAR[*]}"
而不是[@]
表单,因为此处不需要/指向保留分词符号。如果你想以明确的形式打印/记录命令,那将会非常麻烦。
此外,这使您无法在构建的命令中构建重定向或其他特殊的shell选项 - 如果将>outfile
添加到FILES数组,它将被视为另一个命令参数,而不是shell重定向。如果您需要以编程方式构建这些,请为头痛做好准备。
答案 2 :(得分:0)
getopts
应该能够正确处理参数中的空格("file name.txt"
)。假设它们被正确转义(ls -b
)。奇怪的字符应该也可以正常工作。