$*
相当于$1 $2 $3...
- 在所有空格中分开。
"$*"
相当于"$1 $2 $3..."
- 此处没有拆分。
"$@"
相当于"$1" "$2" "$3"...
- 对参数进行拆分(每个参数都单独引用)。
如何引用$(command)
以便它以与"$@"
处理参数相同的方式处理命令的输出行?
我想解决的问题:
我有一个备份函数,它通过参数获取文件并对每个文件进行备份(例如:backup file1 file_2 "file 3"
)。我想快速备份另一个命令返回的文件。
mycmd
返回三个文件(每行一个): file1 , file_2 和 file 3 (包含空格)。如果我运行以下命令:
backup $(mycmd)
它等同于运行backup file1 file_2 file 3
并且由于不存在的文件 file 和 3 而导致错误。但是这样运行:
backup "$(mycmd)"
相当于run:
backup "file1 file_2 file 3"
它们都不够好。
如何使用命令替换来获得相当于backup "file1" "file_2" "file 3"
的调用?
目前我唯一的解决方法是:
while read line; do backup "$line"; done < <(mycmd)
答案 0 :(得分:6)
这是因为命令替换计算为C字符串。 C字符串可以包含除NUL之外的任何字符。
您的预期用例是生成文件名列表。单个文件名还包含除NUL之外的任何字符(也就是说:流行操作系统上的文件名允许包含文字换行符)。
对于其他命令行参数也是如此:foo$'\n'bar
作为参数列表元素完全有效。
因此,在命令替换的输出中表示任意文件名,因此几乎不可能(不使用商定的转义机制或更高级别的格式,一个工具知道如何生成而另一个知道如何解析)。
如果您希望流安全地包含任意文件名列表,则应该以NUL分隔。例如,这是由find -print0
或简单命令printf '%s\0' *
生成的格式。
但是,您无法将其读入shell变量,因为(再次)shell变量不能包含NUL字符文字。您可以做的是将其读入数组:
files=( )
while IFS= read -r -d '' filename; do
files+=( "$filename" )
done < <(find . -name '*.txt' -print0 )
然后展开该数组:
backup "${files[@]}"
上面说过,你可以将一系列行读入一个数组,并扩展该数组(但这里的情况并不安全,数据是任意文件名) :
# readarray is a bash 4.0 builtin
readarray -t lines <(printf '%s\n' "first line" "second line" "third line")
printf 'Was passed argument: <%s>\n' "${lines[@]}"
将正确发出:
Was passed argument: <first line>
Was passed argument: <second line>
Was passed argument: <third line>
答案 1 :(得分:1)
for file in *; do backup "$file"; done
会做正确的事情并完全是POSIX。
要回答您的问题,您可以对输出的字符串使用标准IFS分割。 IFS通常是''$'\ t'$'\ n'。也许单独拆分选项卡或换行符可以解决您的问题。 或者,您可以尝试拆分极不可能的字符,例如垂直制表符:
#ensure the outputed items are separated by the char we'll be splitting on
output=$(printf 'a b\vb\vc d')
set -f #disable glob expansion
IFS=$'\v'
printf "'%s' " $output; printf '\n'
以上打印'a b' 'b' 'c d'
。