【问题标题】:Combine find, grep and xargs with printf将 find、grep 和 xargs 与 printf 结合使用
【发布时间】:2020-02-28 06:48:57
【问题描述】:

我有一个结合了 exec grep 和 printf 选项的 find 命令:

find  -L /home/blast/dirtest -maxdepth 3  **-exec grep -q  "pattern" {} \;**  -printf '%y/#/%TY-%Tm-%Td %TX/#/%s/#/%f/#/%l/#/%h\n' 2> /dev/null

结果:

f/#/2018-01-01 10:00:00/#/191/#/filee.xml/#//#//home/blast/dirtest/01/05

我需要 printf 一次获取所有所需的文件信息(日期、类型大小等)

上面的命令工作正常。但是 exec 选项与 xargs 相比太慢了。

我尝试对 xarg 做同样的事情,但没有成功。 关于如何实现的任何想法?使用 xargs 命令保留所需的 printf 或类似命令。

谢谢

【问题讨论】:

  • 如果xargs 不起作用,你怎么知道-execxargs 慢?
  • @jhnc 我可以用“find”管道 xargs 命令,但我不知道如何将它与 printf 结合起来( printf 是 find 的一个选项)。对于性能,互联网上有很多测试证实 xargs 的速度与 -exec 相比。

标签: unix grep find printf xargs


【解决方案1】:

你的代码是:

find  -L /home/blast/dirtest -maxdepth 3 \
    -exec grep -q  "pattern" {} \; \
    -printf '%y/#/%TY-%Tm-%Td %TX/#/%s/#/%f/#/%l/#/%h\n' 2> /dev/null

这将为每个文件调用一个新的grep 进程。

如果您使用 GNU 实用程序,您可以通过以下方式减少 grep 进程的数量:

(
    format=\''%y/#/%TY-%Tm-%Td %TX/#/%s/#/%f/#/%l/#/%h\n'\'

    find -L /home/blast/dirtest -maxdepth 3 -print0 |\
    xargs -0 grep -l -Z "pattern" |\
    xargs -0 sh -c 'find "$@" -printf '"$format" --
) 2>/dev/null
  • 为清楚起见,将格式字符串存储在变量中
  • 使用 -print0 / -0 / -Z 选项启用以空值分隔的数据
  • 使用find 生成初始文件列表
  • 使用grep 过滤"pattern"(使用xargs 可以最大限度地减少调用grep 的次数)
  • 将过滤后的文件列表输入另一个xargs 以运行最少数量的find -printf
  • 在第二个xargs 中,调用子shell 以便可以附加额外的参数(find 要求路径位于运算符之前)
  • sh -c 调用的虚拟第二个参数 (--) 可防止第一个文件名因分配给 $0 而丢失

【讨论】:

  • 我生活,有时我学习。您对所需的语义是正确的。
  • 我更喜欢这个解决方案而不是第一个答案。即使有多个 xargs 管道,性能也更好。但我发现 -exec 选项只能运行一次命令。正如我在下面的回答中提到的那样
  • 是的,你可以用find ... -exec grep ... '{}' \+ | xargs ...代替find ... | xargs grep ... | xargs ...。性能应该相似。使用哪种可能是口味问题。
【解决方案2】:

完全按照你的意愿去做:

find  -L /home/blast/dirtest/ -maxdepth 3 \
    -printf '%p@%y/#/%TY-%Tm-%Td %TX/#/%s/#/%f/#/%l/#/%h\n' \
    > tmp.out
cut -d@ -f1 tmp.out \
    | xargs grep -l "pattern" 2>/dev/null \
    | sed 's/^/^/; s/$/@/' \
    | grep -f /dev/stdin tmp.out \
    | sed 's/^.*@//'

这假设您的文件名中没有字符 @

它的作用是首先避免使用 grep,然后将所有带有请求的元数据的文件转储到一个临时文件中。

但它还在每一行前面加上完整路径 (%p@)。

然后我们从这个列表中提取 (cut) 完整路径并列出包含模式 (xargs grep) 的文件。

然后,我们使用 sed 为每个此类文件名添加前缀 ^ 并添加后缀 @,这使其成为我们的 tmp.out 文件中的可识别模式。

然后我们使用这个模式 (grep -f /dev/stdin) 从 tmp.out 的大列表中只提取那些路径。

现在剩下的就是删除我们使用最后一个 sed 命令前缀的人工完整路径。

看看您如何使用/home,您很有可能使用的是 Linux,如果您愿意接受一些输出格式更改,那么您可以更优雅地进行操作:

find -L /home/blast/dirtest/ -maxdepth 3 \
    | xargs grep -l "pattern" 2>/dev/null \
    | xargs stat --printf '%F/#/%y/#/%s/#/%n\n'

stat --printf 的输出与find -printf 的输出不同(与 MacOS 的stat -f 的输出不同),但信息相同。

但是请注意,因为您通过 -L 进行查找,并且您正在获取结果:

  1. 结果仅限于可以 grep 的文件类型,因此它们永远不会是目录、链接等。
  2. 如果您偶然发现一个断开的链接,它不会出现在输出中,因为它无法被 grep。

【讨论】:

    【解决方案3】:

    我发现了一个关于 -exec 选项的有趣的事情。 我们可以使用带有加号 (+) 的 exec 运行一次 grep

    -exec command {} +
                  This variant of the -exec option runs the specified command on the selected files, but the command line is built by appending each selected file name at the end; the  total
                  number  of  invocations  of  the  command  will be much less than the number of matched files.  The command line is built in much the same way that xargs builds its command
                  lines.  Only one instance of ’{}’ is allowed within the command.  The command is executed in the starting directory.
    

    这意味着如果我改变这个:

    -exec grep -l 'pattern'  {} \;
    

    这样(用加号替换分号):

    -exec grep -l 'pattern'  {} \+
    

    将显着提高性能。

    那么我只能通过管道传输一个 xargs 来满足格式打印的需要。

    【讨论】:

      猜你喜欢
      • 2021-04-18
      • 2021-08-17
      • 1970-01-01
      • 2019-03-17
      • 1970-01-01
      • 2018-11-29
      • 2018-07-30
      • 2012-09-18
      • 1970-01-01
      相关资源
      最近更新 更多