在 find -exec 调用中执行用户定义的函数

rah*_*hmu 35 solaris ksh find shell-script function

我在 Solaris 10 上使用 ksh (88)、bash (3.00) 和 zsh (4.2.1) 测试了以下内容。

以下代码不会产生任何结果:

function foo {
    echo "Hello World"
}

find somedir -exec foo \;
Run Code Online (Sandbox Code Playgroud)

这一发现不匹配多个文件(如更换-exec ...-print),并从外面叫时功能完美的作品find调用。

以下是该man find页面的内容-exec

 -exec command 如果执行的命令返回一个,则为真
                     零值作为退出状态。结尾
                     命令必须以转义字符作为标点
                     分号 (;)。命令参数 {} 是
                     替换为当前路径名。如果
                     -exec 的最后一个参数是 {} 而你
                     指定 + 而不是分号 (;),
                     命令被调用的次数更少,
                     {} 替换为路径名组。如果
                     命令的任何调用都会返回一个
                     非零值作为退出状态,找到
                     返回非零退出状态。

我可能会做这样的事情:

for f in $(find somedir); do
    foo
done
Run Code Online (Sandbox Code Playgroud)

但我害怕处理字段分隔符问题。

是否可以从调用中调用 shell 函数(在同一个脚本中定义,让我们不要理会范围问题)find ... -exec ...

我既试过/usr/bin/find/bin/find,得到了相同的结果。

Sté*_*las 34

Afunction是 shell 本地的,因此您需要find -exec生成一个 shell 并在该 shell 中定义该函数,然后才能使用它。就像是:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +
Run Code Online (Sandbox Code Playgroud)

bash允许通过带有 的环境导出函数export -f,因此您可以执行以下操作(在 bash 中):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +
Run Code Online (Sandbox Code Playgroud)

ksh88必须typeset -fx导出函数(不是通过环境),但它只能由由 执行的she-bangless 脚本使用ksh,因此不能与ksh -c.

另一种选择是:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +
Run Code Online (Sandbox Code Playgroud)

也就是说,用于typeset -f转储foo内联脚本中函数的定义。请注意,如果foo使用其他函数,您还需要转储它们。

  • @danielkullmann 在`bash -c 'some-code' abc` 中,`$0` 是`a`,`$1` 是`b`...,所以如果你希望`$@` 是`a, b, c ` 你需要先插入一些东西。因为在显示错误消息时也使用了 `$0`,所以最好使用 shell 的名称或在该上下文中有意义的名称。 (6认同)
  • 你能解释一下为什么在 `-exec` 命令中出现两次 `ksh` 或 `bash` 吗?我理解第一个,但不是第二个。 (2认同)

aec*_*ley 10

使用 \0 作为分隔符并将文件名从生成的命令读入当前进程,如下所示:

foo() {
  printf "Hello {%s}\n" "$1"
}

while read -d '' filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)
Run Code Online (Sandbox Code Playgroud)

这里发生了什么:

  • read -d '' 读取到下一个 \0 字节,因此您不必担心文件名中的奇怪字符。
  • 类似地,-print0使用 \0 而不是 \n 来终止每个生成的文件名。
  • cmd2 < <(cmd1)cmd1 | cmd2除了 cmd2 在主 shell 中运行而不是在子 shell 中运行之外,与 cmd2相同。
  • 对 foo 的调用被重定向,/dev/null以确保它不会意外地从管道中读取。
  • $filename 被引用,因此外壳不会尝试拆分包含空格的文件名。

现在,read -d<(...)在zsh中时,bash和ksh 93u,但我不知道早期版本KSH。


Gil*_*il' 6

这并不总是适用,但如果适用,这是一个简单的解决方案。设置globstar选项(set -o globstar在 ksh93 中,shopt -s globstar在 bash 中?4;在 zsh 中默认启用)。然后使用**/递归匹配当前目录及其子目录。

例如,find . -name '*.txt' -exec somecommand {} \;您可以运行

for x in **/*.txt; do somecommand "$x"; done
Run Code Online (Sandbox Code Playgroud)

相反find . -type d -exec somecommand {} \;,您可以运行

for d in **/*/; do somecommand "$d"; done
Run Code Online (Sandbox Code Playgroud)

相反find . -newer somefile -exec somecommand {} \;,您可以运行

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done
Run Code Online (Sandbox Code Playgroud)

**/对您不起作用时(因为您的外壳没有它,或者因为您需要一个find没有外壳类似物的选项),请find -exec参数中定义函数


h3r*_*ler 5

如果您希望从脚本生成的子进程使用预定义的 shell 函数,则需要将其导出 export -f <function>

注意:export -f是 bash 特定的

因为只有shell才能运行shell函数:

find / -exec /bin/bash -c 'function "$1"' bash {} \;
Run Code Online (Sandbox Code Playgroud)

编辑:基本上你的脚本应该是这样的:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;
Run Code Online (Sandbox Code Playgroud)

  • 切勿在 shell 代码中嵌入 `{}`!这意味着文件名被解释为 shell 代码,所以非常危险 (4认同)