xargs:为每一行运行一个命令并用空格分隔参数?

xargs:为每一行运行一个命令并用空格分隔参数?

我可以使用 wsl.exe 在 cygwin bash 和 oh-my-zsh 上重现此问题。我的目的是在命令行上运行这两行:

npm rm plugin-alias
npm i -D plugin-alias

我正在尝试这样做:

echo -ne "rm\0i -D\0" | xargs -t -0 -IREPLACE npm REPLACE plugin-alias

运行rm良好。-t说它正在运行npm rm plugin-alias。但i -D错误:

npm 'i -D' plugin-alias
Unknown command: "i -D"

i -D有没有办法让 xargs作为多个参数传递,以便它运行npm i -D plugin-alias

我尝试将分隔符更改为,\n,但似乎没有什么区别。

如果我必须sed删除引号,那就这样吧,但我希望xargs能够解决它似乎引入的问题。


更新:@muru 提供了有用的评论:

@muru:没有涉及实际的报价

我:那为什么我会收到错误消息,未知命令:“i -D”?

@muru:因为 xargs 提供 i -D 作为单一论点到 npm。

现在我明白了,我可以编辑我的标题以使其更准确。为了更好的可搜索性,我想说原来的标题是“为什么 xargs 引用带有空格的项目以及如何防止它?”

答案1

回答:

xargs:为每一行运行一个命令并用空格分隔参数?

在主题中,这正是-L¹ 选项的用途。

xargs -L1 cmd << 'EOF'
1 2 3
first 'second arg'
"a 1" b\
2
EOF

cmd将为每个运行1 L伊内输入的(逻辑行,您将看到第三行由两条物理行组成,因为我们引用了第二个参数中嵌入的换行符,并且尾随空白允许打破长行),将每一行拆分为未加引号的空白上的参数。

-I但它不能与GNU 风格结合-0

由于它理解某种形式的引用(请注意,它的语法与任何现代 shell 的语法不同),因此它允许我们指定任意参数(限制取决于实现²),因此我们不需要 -0.

对于 BSD xargs,它可以与 while 结合使用-J,这样你就可以这样做:

xargs -rL1 -J {} npm {} plugin-alias << 'EOF'
rm
i -D
EOF

对于其他实现,您可以用来sh重新排序参数:

xargs -L1 sh -c '[ "$#" -eq 0 ] || exec npm "$@" plugin-alias' sh << 'EOF'
rm
i -D
EOF

(这里也用作[ "$#" -eq 0 ] ||非标准的替代品-r)。


1 GNUxargs至少有-l一个已弃用且非标准的-L 1.-l也可以采用多行,例如-L,但必须将其附加到选项(-l1,而不是-l 1;而您可以使用-L1-L 1)。

² 例如,有些对非文本输入感到窒息,有些对命令行或单个参数的长度有极低的限制,有些理解_为文件结尾......还要注意被识别为分隔符的字符列表随实施和区域设置与一些。

答案2

通常并不建议使用它,您可以为此应用 shell 的分词:

printf "rm\0i -D\0" | 
   xargs -t -0 -I{} sh -f -c 'npm $1 plugin-alias' sh {}  

在这里,我将每个 NUL 分隔的字符串作为单个参数传递给sh脚本,并且不带引号的扩展$1导致它们在空格上分割。选项-f(for set -f) 禁用通配符。

使用脚本清楚地查看不同的命令行参数:

% printf "rm\0i -D\0" | 
   xargs -t -0 -I{} sh -f -c './args.sh npm $1 plugin-alias' sh {}
sh -f -c ./args.sh npm $1 plugin-alias sh rm
3 args: <npm> <rm> <plugin-alias> 
sh -f -c ./args.sh npm $1 plugin-alias sh i -D
4 args: <npm> <i> <-D> <plugin-alias> 

或者,我认为-L如果您将 NUL 分隔符更改为换行符,则可以执行您想要的操作。但似乎你仍然需要一个 shell 来以正确的顺序获取参数,因为-I似乎禁用了空格的分割:

% printf 'rm\ni -D\n' |xargs -L1 sh -c './args.sh npm "$@" plugin-alias' sh  
3 args: <npm> <rm> <plugin-alias> 
4 args: <npm> <i> <-D> <plugin-alias> 

无论如何,我不确定 的版本之间是否存在差异xargs,但您可以仅使用 shell 执行相同的操作:

$ printf 'rm\ni -D\n' | while read -r line ; do set -f; ./args.sh npm $line plugin-alias; done
3 args: <npm> <rm> <plugin-alias> 
3 args: <npm> <i -D> <plugin-alias> 

args.sh这是这个脚本:

#!/bin/sh
if [ "$#" != 0 ]; then
    printf "%d args: " "$#"
    printf "<%s> " "$@"
    echo
else
    printf "no args.\n"
fi

答案3

xargs在这里,也许您可​​以让 shell 读取该列表,而不是使用。和zsh

while IFS=, read -rd '' -u3 -A args; do
  npm "$args[@]" plugin-alias
done 3< <(printf '%s\0' rm i,-D)

这里用 分隔参数,,以便更容易指定空参数。

这是您想要代表的列表的列表。外部命令的任意参数的单个列表可以通过用 NUL 字符分隔来表示,因为命令参数具有不能包含 NUL 字符的限制(bash 内置函数和函数的参数具有相同的限制,而不是 zsh 的参数) ,但要表示任意元素列表的列表,您不能依赖单个分隔符,您需要使用某种形式的编码。

一种选择是使用read不带-r.例如,如果您删除-r上面的选项,则可以表示 2 个列表,第一个列表包含空字符串 和foo,bar,第二个列表包含换行符和反斜杠:

printf '%s\0' ',foo\,bar' '
,\\'

您需要记住在,\上加上前缀\while。

请注意,在 中zsh,一条空记录被拆分为一个空元素。在 bash 或 ksh 中,根本没有任何元素,同时foo,被分成 2 个记录:foo和一个空元素。另请注意,对于bash,您需要-a代替-A

相关内容