我可以使用 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
。