【问题标题】:Can xargs execute a subshell command for each argument?xargs 可以为每个参数执行一个子shell命令吗?
【发布时间】:2015-05-30 11:16:24
【问题描述】:

我有一个命令正在尝试为文件生成 UUID:

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

但在结果中,xargs 只执行了一次$(uuid) 子shell:

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

是否有单行(即不是函数)让xargs 在每个输入上执行子shell 命令?

【问题讨论】:

  • @TomFenech: -n 1 实际上会被任何空格分割,无论是否行内,因此该命令会中断带有嵌入空格的路径; -L 1 更接近意图,因为它执行逐行处理,但仍将分词应用于每一行,因此可能多个参数传递给echo per输入行(可能会也可能不会导致问题)。稳健的方法是使用-I,如已接受的答案所示。

标签: bash xargs


【解决方案1】:

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

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

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

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

没有xargs

【讨论】:

  • 干得好;但不仅-n 1 是多余的,因为-I 意味着逐行处理,-n 1 实际上会被 any 空格分割,无论是否行内。虽然-L 1 确实执行逐行处理,但仍对每一行应用分词,而-I 将整行视为单个 参数。
【解决方案2】:

带有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 的开销。如果我可以拆分信用,我会的。谢谢。
  • @mklement0 非常感谢;无论如何,我认为丢弃循环的这个更好
  • 做得好 - 速度更快。顺便说一句:您(和 OP)在哪个平台上拥有 uuid 实用程序?在类似 BSD 的系统和 Linux 上,它是 uuidgen。有趣的是,BSD awk-F'\0' 解释为 -F ''(即 空字符串),因此将行拆分为单个字符(但是,编写的 find 命令不起作用无论如何,使用 BSD find)。
  • @mklement0 这是这个程序ossp.org/pkg/lib/uuid 在我的情况下为fedora 打包;和 GNU findutils 4.5.12
  • @mklement0 ...我的 uuid 在 Ubuntu 的同一个 findutils 包中。有趣的是,uuid 实用程序创建基于时间的 id,而 uuidgen 创建基于随机的 id(默认情况下)。当在循环中运行时,这会导致明显不同的输出 - uuid 创建一组非常相似的 id,uuidgen 创建更多随机值。
【解决方案3】:

hek2mgl's answer很好地解释了问题,他的解决方案也很有效;这个答案着眼于性能

接受的答案有点慢,因为它为每个输入行创建一个bash 进程。

虽然 xargs 通常比 shell 代码循环更可取且速度更快,但在这种特殊情况下,角色是相反的,因为每次迭代都需要 shell 功能。

以下替代解决方案使用while 循环来处理输入行,并且在我的机器上,它的速度大约是xargs 解决方案的两倍

find . -printf "%P\n" | sort | while IFS= read -r f; do echo "$(uuid) $f"; done

如果您担心带有嵌入换行符的文件名(非常罕见)并使用 GNU 实用程序,您可以使用 NUL 字节作为分隔符:

find . -printf "%P\0" | sort -z | while IFS= read -d '' -r f; do echo "$(uuid) $f"; done

更新最快方法是根本不使用shell循环,ᴳᵁᴵᴰᴼ's clever answer证明了这一点强>。 有关他的答案的便携式版本,请参见下文。


兼容性说明:

OP 的 find 命令暗示使用 GNU find (Linux),并使用可能无法在其他平台上运行的功能 (-printf)。

这是ᴳᵁᴵᴰᴼ's answer便携版,它仅使用find(和awk)的符合POSIX 标准的功能。
但是请注意,uuid 不是 POSIX 实用程序;由于 Linux 和类似 BSD 的系统(包括 OSX)有一个 uuidgen 实用程序,该命令使用它来代替:

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

【讨论】:

    猜你喜欢
    • 2016-08-11
    • 2017-04-03
    • 2021-05-24
    • 2021-11-08
    • 2013-06-01
    • 2011-10-12
    • 2010-09-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多