将每个查找结果作为带前缀的参数传递给 exec

将每个查找结果作为带前缀的参数传递给 exec

我想找到一些文件并将它们转换为命令的参数链。到目前为止,我所做的是:

find . -name '*.yml' -exec cmd -f {} \+

结果是

cmd -f ./foo.yml ./bar.yml

但我真正想要的是

cmd -f ./foo.yml -f ./bar.yml

(注意第二点-f)。我怎样才能实现这个目标?

答案1

解决方案

find无法直接执行此操作。您需要一个内壳来重建参数数组:

find . -name '*.yml' -exec sh -c '
   marker=x
   for f do
      [ "$marker" ] && { set --; marker=''; }
      set -- "$@" -f "$f"
   done
   exec cmd "$@"
' find-sh {} +

可能的问题

我认为在某些情况下上述代码可能会失败。问题源于命令不能任意长(12)。find … -exec foo … {} +应该足够智能,不会达到限制,foo如果需要,它应该运行多次(xargs应该同样智能)。我们的foosh,然后是-c,然后是我们的shell代码,然后find-sh然后可能有多个路径名。

cmd …相比之下,我们的最终方法是剥离-cshell 代码和find-sh,这样可以缩短命令;但每个路径名都有一个附加部分-f,这样可以延长命令。如果延长“获胜”,那么命令可能会变得太长。

为了缓解这种情况,您可以向 shell 代码中注入空格行,这样代码占用更多字节,从而补偿-f稍后添加的更多字节。如果不知道确切的限制,就不可能创建真正强大的代码。在我看来,注入空格并不是一个好方法。

find更好的方法是(滥用)使用(在我们的例子中)给出的起点.。我不是程序员,我不知道附加项-f实际上占用多少空间。它是两个字符,可能还有某种终止符/分隔符,我猜测总共不超过四个字符。因此我会这样做:

find .///. -name '*.yml' -exec sh -c '
   marker=x
   for f do
      [ "$marker" ] && { set --; marker=''; }
      set -- "$@" -f "${f#.///}"
   done
   exec cmd "$@"
' find-sh {} +

现在每个路径名都应该以.///.而不是.(和仍然等效相当于./..。稍后在 shell 代码中,我们从每个路径名的开头删除这四个额外的字符("${f#.///}"),以补偿-f每个路径名添加一个字符。这样,如果 构建的命令find -exec在限制范围内,那么我们的cmd …命令也将在限制范围内。

再次强调,我不是程序员,我不确定四个字符是否足够(或者是否取决于架构等)*。不过我相信这种方法可能有用。

但请注意,使用.///.代替.可能一般来说需要您调整一些测试(例如-path-regexGNU 的find)。-name '*.yml'您使用的不需要调整。


* 发布答案后,我收到一条评论,声称我需要至少考虑每个 11 个字节-f

一个额外的-f参数将添加"-f"aka['-', 'f', '\0']字符串的这 3 个字节,还有一个额外的指针(现在通常是 8 个字节),在大多数系统上这都考虑到了限制execve(),所以至少是 11 个字节

这超出了我的专业知识范围,我无法判断它是否属实;同时我完全相信评论的作者是想帮助我们,而不是欺骗我们。所以是的,你很可能需要更多的斜线。

相关内容