我使用的是 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
定义。请注意,如果使用其他函数,您也需要转储它们。foo
foo
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
在参数中定义函数。