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

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

我使用的是 Solaris 10,并且使用 ksh (88)、bash (3.00) 和 zsh (4.2.1) 测试了以下内容。

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

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

该 find 确实匹配了多个文件(如替换为 所示-exec ...-print,并且该函数在从find调用外部调用时可以完美运行。

man find该页面的内容如下-exec

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

我可能可以做这样的事情:

for f in $(find somedir); do
    foo
done

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

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

我尝试了两者/usr/bin/find/bin/find得到了相同的结果。

答案1

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

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bash允许通过环境导出函数export -f,所以你可以(在 bash 中):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88必须typeset -fx导出函数(不是通过环境),但只能由 执行的 she-bang less 脚本使用ksh,所以不能使用ksh -c.

另一种选择是执行以下操作:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

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

ps -f或者,您可以通过环境变量传递函数定义,而不是在命令行上传递函数定义(例如,这在输出中可见):

FUNCDEFS=$(typeset -f foo) find ... -exec ksh -c '
  eval "$FUNCDEFS" &&
    unset -v FUNCDEFS &&
    foo "$@"' ksh {} +

unset -v FUNCDEFS避免污染该函数启动的命令的环境foo(如果有))。

答案2

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

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

while IFS= read -d '' -r filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

这里发生了什么:

  • read -d ''读取直到下一个 \0 字节,因此您不必担心文件名中的奇怪字符。
  • 类似地,-print0使用 \0 而不是 \n 来终止每个生成的文件名。
  • 选项-r(raw) 防止文件名中的反斜杠转义
  • cmd2 < <(cmd1)与 cmd2 相同cmd1 | cmd2,只是 cmd2 在主 shell 中运行而不是在子 shell 中运行。
  • 对 foo 的调用被重定向,/dev/null以确保它不会意外地从管道中读取。
  • $filename被引用,因此 shell 不会尝试分割包含空格的文件名。
  • IFS=防止 Bash 从文件名中去除尾随空格(感谢丹尼尔·斯梅德加德·布斯对于此更正)

现在,read -d并且<(...)在 zsh、bash 和 ksh 93u 中,但我不确定早期的 ksh 版本。

答案3

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

注意:export -f是 bash 特定的

因为只有一个能跑功能:

find / -exec /bin/bash -c 'function "$1"' bash {} \;

编辑:本质上你的脚本应该类似于这样:

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

答案4

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

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

for x in **/*.txt; do somecommand "$x"; done

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

for d in **/*/; do somecommand "$d"; done

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

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

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

相关内容