`find -exec` 中的这个奇怪语法是什么?

What is this strange syntax inside `find -exec`?

最近我遇到了一个奇怪的bash 脚本,它用于从find -exec 内部调用自定义bash 函数。我开发了以下简单脚本来演示我需要解释的功能。

在下面的示例中,将为每个 find 个结果调用函数 foo

foo()                                                                                                                                                 
{
    echo "$@"
}
export -f foo

find . -exec bash -c 'foo "$@"' bash {} \;

有人可以解释一下 -exec 之后的部分是如何解释的吗?


更新:

为了进一步简化,在如上所述导出 foo 之后,对每个 find 结果执行以下操作(假设有一个名为 my_file 的文件)。

bash -c 'foo "$#"' bash my_file

这会产生输出 myfile。我不明白这是怎么回事。第二个 bash 在那里做什么?任何详细的解释表示赞赏。

(请注意,这个问题不是关于 find 命令的。另外请忽略函数 foo 的功能,我只是想导出一些函数)

要理解你需要知道 4 件事:

  1. find 操作 -exec 允许您对找到的文件和目录应用命令。

  2. -cbash选项记录如下:

    BASH(1)
    ...
    OPTIONS
    ...
           -c        If the -c option is present, then commands are read from
                     the first non-option  argument  command_string.
                     If  there are arguments after the command_string, they
                     are assigned to the positional parameters, starting with [=10=].
    
    ...
    
    If bash is started with the -c option, then [=10=] is set to the first
    argument after the string to be executed, if one is present.
    Otherwise, it is set to the filename used to invoke bash, as given
    by argument zero.
    
  3. bash 中,$@ 扩展为从参数 [=23= 开始的所有位置参数(</code>、<code>...) ].

  4. bash 函数中,位置参数是调用函数时传递给函数的参数。

因此,在您的情况下,为每个找到的文件或目录执行的命令是:

bash -c 'foo "$@"' bash <the-file>

位置参数因此设置为:

[=12=] = bash
 = <the-file>

bash 被要求在此上下文中执行 'foo "$@"'"$@" 首先展开为 "<the-file>"。因此,函数 foo 被调用时带有一个参数:"<the-file>"。因此,在函数 foo 的上下文中,位置参数为:

 = "<the-file>"

echo "$@" 扩展为 echo "<the-file>"

所有这些只是打印所有找到的文件或目录的名称。这几乎就像你有任何:

find . -exec echo {} \;
find . -print
find .
find

(对于接受最后一个的 find 版本)。

几乎和 一样,只是因为如果文件或目录名称包含空格,根据您对 find 和引号的使用,您会得到不同的结果。所以,如果你打算有一个更复杂的 foo 函数,你应该注意引号。示例:

$ touch "filename with spaces" plain
$ ls -1
filename with spaces
plain                                                # 2 files
$ foo() { echo "$@"; }                               # print arguments
$ find . -type f
./filename with spaces
./plain
$ find . -type f -exec bash -c 'foo "$@"' bash {} \;
./filename with spaces
./plain
$ find . -type f -exec bash -c 'foo $@' bash {} \;
./filename with spaces
./plain

3 find 命令显然做同样的事情但是:

$ bar() { echo $#; }                                 # print number of arguments
$ wc -w < <(find . -type f)
4                                                    # 4 words
$ find . -type f -exec bash -c 'bar "$@"' bash {} \;
1                                                    # 1 argument
1                                                    # per file
$ find . -type f -exec bash -c 'bar $@' bash {} \;
3                                                    # 3 arguments
1                                                    # 1 argument

对于 find . -type f -exec bash -c 'bar "$@"' bash {} \;,第一个文件名作为一个参数传递给函数 bar,而在所有其他情况下,它被视为 3 个单独的参数。