2

$*相当于$1 $2 $3...- 在所有空格上拆分。

"$*"相当于"$1 $2 $3..."- 这里没有分裂。

"$@"等效于"$1" "$2" "$3"...- split on arguments(每个参数都单独引用)。

如何引用$(command) 以便它以与"$@"处理参数相同的方式处理命令的输出行?

我要解决的问题:

我有一个备份功能,它通过参数获取文件并备份每个文件(例如:)backup file1 file_2 "file 3"。我想快速备份另一个命令返回的文件。

mycmd返回三个文件(每行一个):file1file_2file 3(包含一个空格)。如果我运行以下命令:

备份 $(mycmd)

它相当于运行,并且由于不存在文件file3backup file1 file_2 file 3会导致错误。但是以这种方式运行它:

备份“$(mycmd)”

相当于运行:

备份“文件1
文件_2
文件 3"

他们都不够好。

如何使用命令替换来获得等效于:的调用backup "file1" "file_2" "file 3"

目前我唯一的解决方法是:

而读线;备份“$line”;完成<<(mycmd)
4

2 回答 2

6

文件名列表(或命令行参数)不能安全地以面向行的格式传递而无需转义。

这是因为命令替换计算为 C 字符串。AC 字符串可以包含除 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>
于 2017-07-27T20:00:33.383 回答
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'

于 2017-07-27T20:19:29.480 回答