例子:
% touch -- safe-name -name-with-dash-prefix "name with space" \
'name-with-double-quote"' "name-with-single-quote'" \
'name-with-backslash\'
xargs
似乎无法处理双引号:
% ls | xargs ls -l
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
ls: invalid option -- 'e'
Try 'ls --help' for more information.
如果我们使用该-0
选项,则带有破折号前缀的名称会出现问题:
% ls -- * | xargs -0 -- ls -l --
ls: invalid option -- 'e'
Try 'ls --help' for more information.
这是在使用其他可能有问题的字符(例如换行符、控制字符等)之前的情况。
答案1
这POSIX规范确实给了你一个例子:
ls | sed -e 's/"/"\\""/g' -e 's/.*/"&"/' | xargs -E '' printf '<%s>\n'
(文件名是任意序列字节(除了/
NULL)和sed
/xargs
期待文本,您还需要将语言环境修复为 C(其中所有非 NUL 字节都将生成有效字符)以使其可靠(除了对xargs
参数最大长度具有非常低限制的实现之外))
-E ''
对于某些xargs
实现来说,如果没有它,则需要它来理解表示_
输入结束的参数(例如仅echo a _ b | xargs
输出)。a
使用 GNU xargs
,您可以使用:
ls | xargs -rd '\n' printf '<%s>\n'
(还添加-r
(也是 GNU 扩展)如果输入为空则不会运行该命令)。
GNUxargs
还有一个-0
已被其他一些实现复制的,因此:
ls | tr '\n' '\0' | xargs -0 printf '<%s>\n'
稍微更便携一些。
所有这些都假设文件名不包含换行符。如果文件名可能带有换行符,则 的输出ls
根本无法进行后处理。如果你得到:
a
b
这可以是 aa
和b
文件,也可以是一个名为 的文件a<newline>b
,没有办法分辨。
GNUls
有一个--quoting-style=shell-always
使得它的输出明确并且可以进行后处理,但是引用与 期望的引用不兼容xargs
。xargs
识别"..."
和引用的形式\x
。'...'
但 和"..."
都是'...'
强引号,不能包含换行符(只能\
转义 换行符xargs
),因此与 sh 引用不兼容,其中只有'...'
强引号(并且可以包含换行符),但是\<newline>
行延续(已删除) ) 而不是转义换行符。
您可以使用 shell 解析该输出,然后以预期的格式输出xargs
:
eval "files=($(ls --quoting-style=shell-always))"
[ "${#files[@]}" -eq 0 ] || printf '%s\0' "${files[@]}" |
xargs -0 printf '<%s>\n'
或者您可以让 shell 获取文件列表并将其以 NUL 分隔传递给xargs
.例如:
和
zsh
:print -rNC1 -- *(N) | xargs -r0 printf '<%s>\n'
和
ksh93
:(set -- ~(N)*; (($# == 0)) || printf '%s\0' "$@") | xargs -r0 printf '<%s>\n'
和
fish
:begin set -l files *; string join0 -- $files; end | xargs -r0 printf '<%s>\n'
和
bash
:( shopt -s nullglob set -- * (($# == 0)) || printf '%s\0' "$@" ) | xargs -r0 printf '<%s>\n'
2023 编辑。自 GNU coreutils 9.0 版本(2021 年 9 月)以来,GNUls
现在有一个--zero
可以与以下命令结合使用的选项xargs -r0
:
ls --zero | xargs -r0 printf '<%s>\n'
答案2
为了xargs
理解-0
空分隔输入选项,发送方还必须将空分隔符应用于他们发送的数据。
否则两者之间就没有同步。
一种选项是GNU find
可以放置此类分隔符的命令:
find . -maxdepth 1 ! -name . -print0 | xargs -0 ls -ld
答案3
正如你所说,xargs
不喜欢不匹配的双引号,除非你使用,-0
但-0
只有当你向它提供空终止数据时才有意义。所以,这失败了:
$ echo * | xargs
xargs: unmatched double quote; by default quotes are special to xargs unless you use the -0 option
name-with-backslash -name-with-dash-prefix
但这有效:
$ printf '%s\0' -- * | xargs -0
-- name-with-backslash\ -name-with-dash-prefix name-with-double-quote" name-with-single-quote' name with space safe-name
无论如何,您的基本方法并不是真正做到这一点的最佳方法。不用摆弄xargs
andls
之类的东西,只需使用 shell glob 即可:
$ for f in *; do ls -l -- "$f"; done
-rw-r--r-- 1 terdon terdon 4142 Aug 11 16:03 a
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-backslash\'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 -name-with-dash-prefix
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name-with-double-quote"'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 "name-with-single-quote'"
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 'name with space'
-rw-r--r-- 1 terdon terdon 0 Aug 11 15:34 safe-name
答案4
尝试这样做是极其愚蠢的解析命令的输出ls
那是不是为解析而设计的提供一个命令不适合处理多个字符(例如:new lines
和{}
)当 shell 自行执行此操作时:
set -- *; for f; do echo "<$f>"; done
set -- *
for f
do ls "$f"
done
或者,在一个命令行中:
$ set -- *; for f; do echo "<$f>"; done
<name-with-backslash\>
<-name-with-dash-prefix>
<name-with-double-quote">
<name-with-single-quote'>
<name with space>
<safe-name>
<with_a
newline>
请注意,输出处理(并且有 n 个示例作为最后一个文件名)与换行符完全没有问题。
或者,如果文件数量使 shell 变慢,请使用 find:
$ find ./ -type f -exec echo '<{}>' \;
<./safe-name>
<./with_a
newline>
<./name-with-double-quote">
<./-name-with-dash-prefix>
<./name with space>
<./name-with-single-quote'>
<./name-with-backslash\>
请注意,find 处理所有点文件和所有子目录的方式与 shell 不同。