xargs能为每个参数执行一个子shell命令吗?

22

我有一个命令,试图为文件生成UUID:

find -printf "%P\n"|sort|xargs -L 1 echo $(uuid)

但实际上,xargs 只会执行一次 $(uuid) 子进程:

8aa9e7cc-d3b2-11e4-83a6-1ff1acc22a7e file1
8aa9e7cc-d3b2-11e4-83a6-1ff1acc22a7e file2
8aa9e7cc-d3b2-11e4-83a6-1ff1acc22a7e file3

有没有一行命令(不是函数)可以让xargs在每个输入上执行子shell命令?


1
@TomFenech:-n 1 实际上会按任何空格分割,无论是行内还是行外,因此带有嵌入式空格的路径将导致命令中断;-L 1 更接近意图,因为它执行逐行处理,但仍然对每行应用单词拆分,因此可能会向 echo 传递多个参数(这可能会导致问题)。稳健的方法是使用 -I,就像被接受的答案一样。 - mklement0
3个回答

24

这是因为$(uuid)会在当前shell中扩展。您可以显式调用一个shell:

find -printf "%P\n"| sort | xargs -I '{}' bash -c 'echo $(uuid) {}'

顺便说一下,我会使用以下命令:

find -exec bash -c 'echo "$(uuid) ${1#./}"' -- '{}' \;

没有 xargs


2
做得很好,但是-n 1是多余的,因为-I意味着逐行处理,-n 1实际上会将_任何_空格分隔开,无论是否在行内。虽然-L 1确实会进行逐行处理,但是对于每一行仍会应用单词分割,而-I则将整行作为一个_单独_参数处理。 - mklement0

6
hek2mgl的回答很好地解释了问题,并且他的解决方案很有效;而这个答案关注性能
被采纳的答案略慢,因为它为每个输入行创建一个bash进程。
虽然xargs通常比shell代码循环更可取且更快,但在这种特定情况下,角色被颠倒了,因为需要在每次迭代中使用shell功能。
以下替代解决方案使用while循环来处理输入行,在我的机器上,它比xargs解决方案快两倍
find . -printf "%P\n" | sort | while IFS= read -r f; do echo "$(uuid) $f"; done

请注意使用while而不是for,因为for无法强大地解析命令输出(简而言之:带有嵌入式空格的文件名会破坏命令-请参见http://mywiki.wooledge.org/DontReadLinesWithFor)。 如果您担心带有嵌入式换行符(非常罕见)的文件名并使用GNU实用程序,则可以使用NUL字节作为分隔符:
find . -printf "%P\0" | sort -z | while IFS= read -d '' -r f; do echo "$(uuid) $f"; done
更新:最快的方法是根本不使用shell循环,正如ᴳᵁᴵᴰᴼ的聪明回答所证明的那样。 请参阅下面提供的他回答的可移植版本。

兼容性注意事项:

原帖中的find命令暗示了使用GNUfind(Linux),并使用可能无法在其他平台上正常工作的功能(-printf)。

以下是一个ᴳᵁᴵᴰᴼ的答案的可移植版本,仅使用POSIX兼容的find(和awk)特性。
但是需要注意的是,uuid不是一个POSIX实用程序;由于Linux和类似BSD的系统(包括OSX)都有一个uuidgen实用程序,因此该命令使用该实用程序。

 find . -exec printf '%s\t' {} \; -exec uuidgen \; | 
   awk -F '\t' '{ sub(/.+\//,"", $1); print $2, $1 }' | sort -k2

4

使用for循环:

for i in $(find -printf "%P\n" | sort) ; do echo "$(uuid) $i";  done

编辑:另一种方法是这样的:

find -printf "%P\0" -exec uuid -v 4 \; | sort | awk -F'\0' '{ print $2 " " $1}'

这会输出文件名,后跟uuid(无需子shell),以便进行排序,然后交换由null分隔的两列。

这个也可以工作,并且更容易阅读一些,而且没有在每个参数上开销一个新的bash。如果我能分担一些荣誉就好了。谢谢。 - adelphus
1
@mklement0,非常正确,谢谢。无论如何,我决定这个舍弃循环的方法更好。 - guido
1
干得好,这甚至更快了。顺便问一下:你(和OP)在什么平台上可以使用uuid工具?在类似BSD的系统和Linux中,它是uuidgen。有趣的是,BSD的awk将-F'\0'解释为-F ''(即空字符串),因此将行分隔成单个字符(但是,编写的find命令在BSD find上无法工作)。 - mklement0
1
@mklement0,这是我的程序http://www.ossp.org/pkg/lib/uuid/,在我的情况下打包为Fedora;以及GNU findutils 4.5.12。 - guido
1
@mklement0 ...在我的系统上,uuid与Ubuntu中的findutils软件包相同。有趣的是,uuid实用程序创建基于时间的ID,而uuidgen创建基于随机数的ID(默认情况下)。这导致在循环中运行时产生截然不同的输出 - uuid创建一组非常相似的ID,而uuidgen创建更多的随机值。 - adelphus
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接