我想找到一些文件并将它们转换为命令的参数链。到目前为止,我所做的是:
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 {} +
可能的问题
我认为在某些情况下上述代码可能会失败。问题源于命令不能任意长(1,2)。find … -exec foo … {} +
应该足够智能,不会达到限制,foo
如果需要,它应该运行多次(xargs
应该同样智能)。我们的foo
是sh
,然后是-c
,然后是我们的shell代码,然后find-sh
然后可能有多个路径名。
cmd …
相比之下,我们的最终方法是剥离-c
shell 代码和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
或-regex
GNU 的find
)。-name '*.yml'
您使用的不需要调整。
* 发布答案后,我收到一条评论,声称我需要至少考虑每个 11 个字节-f
:
一个额外的
-f
参数将添加"-f"
aka['-', 'f', '\0']
字符串的这 3 个字节,还有一个额外的指针(现在通常是 8 个字节),在大多数系统上这都考虑到了限制execve()
,所以至少是 11 个字节
这超出了我的专业知识范围,我无法判断它是否属实;同时我完全相信评论的作者是想帮助我们,而不是欺骗我们。所以是的,你很可能需要更多的斜线。