mar*_*ark 6 shell xargs text-processing
观察:
mark@L-R910LPKW:~$ echo a b | xargs -d' ' -I{} bash -c 'echo {} 1'
a 1
b
bash: line 2: 1: command not found
mark@L-R910LPKW:~$
Run Code Online (Sandbox Code Playgroud)
到底是怎么回事?
Gil*_*il' 16
b出现在输出中,因此它已被处理,但不是按照您预期的方式处理。
第一步,让 bash 告诉您它看到了什么:传递选项-x以启用其跟踪。
$ echo a b | xargs -d\' \' -I{} bash -x -c \'echo {} 1\'\n+ echo a 1\na 1\n+ echo b\nb\n+ 1\nbash: line 2: 1: command not found\nRun Code Online (Sandbox Code Playgroud)\n因此 bash 按预期首先被调用echo a 1。但下一行echo b并不echo b 1像你想象的那样。还有一个额外的行1。为什么?
好吧,你告诉 xargs 以空格分割。并且您传递了输入,a b\xe2\x90\xa4其中\xe2\x90\xa4是换行符。因此 xargs 看到输入包含两个片段:a和b\xe2\x90\xa4。按照指示,xargs 对每个片段调用 bash:首先执行echo a 1,然后执行echo b\xe2\x90\xa41。
find或 的某些版本xargs允许您嵌入{}shell 片段。这几乎总是一个坏主意,它会破坏某些文件名或其他数据,并且通常是一个安全漏洞。将数据作为单独的参数传递。
正如Gilles 在他们的回答中提到的,-d ' 'GNU xargs 的选项使它考虑空格,并且只考虑空格作为分隔符,将换行符作为数据的一部分,在这里像字母本身一样嵌入到你的 shell 代码中。那可能不是你想要的。(最常见的用途可能-d是-d '\n'告诉它按原样使用行,而不进行任何进一步的处理,例如-L。)
相反,如果您希望将每个空格分隔的单词作为单独的项目,则一种选择是利用默认行为,即在空格上拆分项目,因此这可以直接工作:
$ echo a b | xargs -n1 bash -c 'echo "$1" 1' sh
a 1
b 1
Run Code Online (Sandbox Code Playgroud)
请注意,它还处理引号和反斜杠(与 shell 略有不同),因此这与仅使用空格作为分隔符不同。输入a "b c"将产生项目a和b c。
或者,您可以使用-dwithtr来预处理输入,并将要用作分隔符的所有字符折叠为一个字符:
$ printf 'a b\nc\n' | tr ' ' '\n' | xargs -d'\n' -n1 bash -c 'echo "$1" 1' sh
a 1
b 1
c 1
Run Code Online (Sandbox Code Playgroud)
然而,-d这不是标准的,我认为只在 GNU xargs 中实现,所以你可能想改用-0具有更广泛支持的:
$ printf 'a b\nc\n' | tr ' \n' '\0' | xargs -0 -n1 bash -c 'echo "$1" 1' sh
a 1
b 1
c 1
Run Code Online (Sandbox Code Playgroud)
无论如何,请避免将值直接嵌入到 shell 代码片段中,因为这是不安全的,并且不可能使用任意值正确工作。