我正在 Stack Overflow 上写一个问题的答案,答案涉及一个for
类似于以下内容的嵌套循环
for jpeg in **/foo.jpg; do
d=${f%/foo.jpg} # Directory containing foo.jpg
for m4a in "$d"/*.m4a; do
someCommand "$m4a" --arg "$jpeg"
done
done
我有这个想法,将整个事情变成一个find
使用的命令其他 find
主要命令-exec
,类似于:
find . -type d -exec test -f {}/foo.jpg \;
-exec find {} -name '*.m4a' \
-exec someCommand ??? --args {}/foo.jpg \;
问题出在???
.我想传递一个文字{}
作为嵌套中的参数,find
而-exec
无需外部find
将其替换为目录名称,就像使用{}/foo.jpg
.
POSIX 标准并没有真正说明将文字{}
作为参数传递给命令。它只说
如果 utility_name 或参数字符串包含两个字符“{}”,而不仅仅是两个字符“{}”,则 find 是否替换这两个字符或不更改地使用该字符串是实现定义的。
这似乎排除了某种涉及的诡计sh -c
,比如
-exec sh -c 'someCommand {} --args "$1"' _ {}/foo.jpg \;
因为{}
可能会或可能不会被外部所取代find
。
find
那么,有没有什么办法可以以这种方式筑巢呢?
答案1
Find没有任何逃逸机制
这一事实不允许您插入{}
现有的-exec
和/或-execdir
选项。替换是用 plain 进行的strncpy()
。 /在 GNU-find (我将exec
其execdir
用作参考)内部通过 执行bc_push_arg
。对于{} +
表格,我们有:
/* "+" terminator, so we can just append our arguments after the
* command and initial arguments.
*/
execp->replace_vec = NULL;
execp->ctl.replace_pat = NULL;
execp->ctl.rplen = 0;
execp->ctl.lines_per_exec = 0; /* no limit */
execp->ctl.args_per_exec = 0; /* no limit */
/* remember how many arguments there are */
execp->ctl.initial_argc = (end-start) - 1;
/* execp->state = xmalloc(sizeof struct buildcmd_state); */
bc_init_state (&execp->ctl, &execp->state, execp);
/* Gather the initial arguments. Skip the {}. */
for (i=start; i<end-1; ++i)
{
bc_push_arg (&execp->ctl, &execp->state,
argv[i], strlen (argv[i])+1,
NULL, 0,
1);
}
{}
它只是将所有内容附加到最后,因为表单中不能有多个 实例{} +
。对于{} ;
我们有:
/* Semicolon terminator - more than one {} is supported, so we
* have to do brace-replacement.
*/
execp->num_args = end - start;
execp->ctl.replace_pat = "{}";
execp->ctl.rplen = strlen (execp->ctl.replace_pat);
execp->ctl.lines_per_exec = 0; /* no limit */
execp->ctl.args_per_exec = 0; /* no limit */
execp->replace_vec = xmalloc (sizeof(char*)*execp->num_args);
/* execp->state = xmalloc(sizeof(*(execp->state))); */
bc_init_state (&execp->ctl, &execp->state, execp);
/* Remember the (pre-replacement) arguments for later. */
for (i=0; i<execp->num_args; ++i)
{
execp->replace_vec[i] = argv[i+start];
}
所以我们有execp->ctl.replace_pat = "{}";
。这一切都在parser.c
将上式替换为:
size_t len; /* Length in ARG before `replace_pat'. */
char *s = mbsstr (arg, ctl->replace_pat);
if (s)
{
len = s - arg;
}
else
{
len = arglen;
}
if (bytes_left <= len)
break;
else
bytes_left -= len;
strncpy (p, arg, len);
p += len;
arg += len;
arglen -= len;
在bc_do_insert()
在buildcmd.c
。
所以不,没有办法逃避{}
。然而,某些版本的 find 不会替代{}/foo
而只能替代{}
其本身,因此您可以将两个不同版本的 find 与-exec sh -c 'someCommad {}'
.
假设gfind
是 GNU-find 并且afind
是 AIX find 你可能可以实现:
afind . -type d -execdir test -f foo.jpg \
-exec sh -c 'gfind . -name "*.m4a" -exec someCommand {} \;' \;
但这将是一个可怕的黑客行为。
不错的解决方法
您面临的问题是您正在执行全局查找以获取目录中某种类型的所有文件。换句话说,您首先找到一个目录,然后将该目录的所有文件通配成一个目录的一部分。单一命令行。
-execdir
这就是要解决的问题。它将在包含找到的文件的目录中运行该命令。例如:
$ mkdir -p a/a b/b c/c d e
$ touch a/a/foo.m4a b/b/foo.m4a
$ touch a/a/bar.m4a b/b/bar.m4a c/c/foobar.m4a
$ touch a/a/yay.jpg c/c/yay.jpg
$ find . -type f -name '*.m4a' -execdir test -e yay.jpg \; \
-execdir echo someCommand --arg yay.jpg {} +
someCommand --arg yay.jpg ./foobar.m4a
someCommand --arg yay.jpg ./bar.m4a ./foo.m4a
此外,我使用的是{} +
表单而不是{} ;
的表单exec
。这会将找到的所有文件(在其运行的目录中)放入同一命令行中。
请注意,该命令未运行,b/b
因为test -e
它被阻止了。