有没有办法find
执行我在 shell 中定义的函数?
例如:
dosomething () {
echo "Doing something with $1"
}
find . -exec dosomething {} \;
结果是:
find: dosomething: No such file or directory
有没有办法让find
's-exec
看到dosomething
?
由于只有 shell 知道如何运行 shell 函数,因此您必须运行 shell 才能运行函数。您还需要用 标记导出函数export -f
,否则子shell 不会继承它们:
export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;
find . | while read file; do dosomething "$file"; done
Jac 的回答很棒,但它有几个容易克服的陷阱:
find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done
这使用 null 作为分隔符而不是换行符,因此带有换行符的文件名将起作用。它还使用-r
禁用反斜杠转义的标志,如果没有它,文件名中的反斜杠将不起作用。它还清除IFS
,以便名称中潜在的尾随空格不会被丢弃。
添加引号{}
如下所示:
export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;
这更正了由 . 返回的特殊字符引起的任何错误find
,例如名称中带有括号的文件。
为了提高效率,许多人xargs
习惯于批量处理结果,但这非常危险。因此,引入了一种替代方法find
,可以批量执行结果。
请注意,尽管此方法可能带有一些警告,例如 POSIX 中的要求 - 在命令末尾具有find
。{}
export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +
find
将许多结果作为参数传递给单个调用,bash
并且for
-loop 遍历这些参数,dosomething
在每个参数上执行函数。
上面的解决方案从 开始参数$1
,这就是为什么有一个_
(代表$0
)。
同样,我认为接受的最佳答案应更正为
export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;
这不仅更加理智,因为参数应该始终以 开头$1
,而且如果返回的文件名对 shell 具有特殊含义,则using$0
可能会导致意外行为。find
让脚本调用自己,将找到的每个项目作为参数传递:
#!/bin/bash
if [ ! $1 == "" ] ; then
echo "doing something with $1"
exit 0
fi
find . -exec $0 {} \;
exit 0
当您自己运行脚本时,它会找到您要查找的内容并调用自己并将每个查找结果作为参数传递。当脚本使用参数运行时,它会执行参数上的命令,然后退出。
对于那些正在寻找将在当前目录中的所有文件上执行给定命令的 Bash 函数的人,我从上述答案中编译了一个:
toall(){
find . -type f | while read file; do "$1" "$file"; done
}
请注意,它会与包含空格的文件名中断(见下文)。
以这个函数为例:
world(){
sed -i 's_hello_world_g' "$1"
}
假设我想将当前目录中所有文件中的所有“hello”实例更改为“world”。我会做:
toall world
为了安全使用文件名中的任何符号,请使用:
toall(){
find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}
(但你需要一个find
可以处理-print0
例如 GNU find
)。
以这种方式执行功能是不可能的。
为了克服这个问题,您可以将您的函数放在一个 shell 脚本中并从find
# dosomething.sh
dosomething () {
echo "doing something with $1"
}
dosomething $1
现在在 find 中使用它:
find . -exec dosomething.sh {} \;
将函数放在一个单独的文件中并开始find
执行它。
Shell 函数位于定义它们的 Shell 内部;find
将永远无法看到他们。
为了对其他一些答案提供补充和说明,如果您使用exec
or execdir
( -exec command {} +
) 的批量选项,并且想要检索所有位置参数,则需要考虑处理$0
with bash -c
。
更具体地说,考虑下面的命令,它bash -c
按照上面的建议使用,并简单地从它找到的每个目录中回显以“.wav”结尾的文件路径:
find "$1" -name '*.wav' -execdir bash -c 'echo "$@"' _ {} +
Bash手册说:
如果
-c
存在该选项,则从第一个非选项参数 command_string 中读取命令。如果 command_string 后面有参数,则将它们分配给位置参数,以 . 开头$0
。
这里,'echo "$@"'
是命令字符串,是命令字符串_ {}
后面的参数。请注意,这$@
是 Bash 中的一个特殊位置参数,它扩展到从 1 开始的所有位置参数。另请注意,使用该-c
选项,第一个参数被分配给位置参数$0
。
这意味着如果您尝试使用 访问所有位置参数$@
,您将只能获得从 开始$1
和 向上的参数。这就是为什么 Dominik 的答案有 的原因_
,它是填充参数的一个虚拟参数$0
,所以如果我们使用$@
参数扩展,或者该for
答案中的循环,我们想要的所有参数都可以在以后使用。
当然,类似于接受的答案,bash -c 'shell_function "$0" "$@"'
也可以通过显式传递来工作$0
,但同样,您必须记住,这$@
不会按预期工作。
供您参考,这里是 bash 下各种解决方案的基准测试,包括一个简单的 for 循环案例:(1465 个目录,在标准硬盘驱动器上,armv7l GNU/Linux synology_armada38x_ds218j)
dosomething() { 回声 $1; }
export -f dosomething
time find . -type d -exec bash -c 'dosomething "$0"' {} \;
real 0m16.102s
time while read -d '' filename; do dosomething "${filename}" </dev/null; done < <(find . -type d -print0)
real 0m0.364s
time find . -type d | while read file; do dosomething "$file"; done
real 0m0.340s
time for dir in $(find . -type d); do dosomething $dir; done
real 0m0.337s
“find | while”和“for loop”在速度上似乎是最好的和相似的。
我发现最简单的方法如下,在一个单一的重复两个命令do
:
func_one () {
echo "The first thing with $1"
}
func_two () {
echo "The second thing with $1"
}
find . -type f | while read file; do func_one $file; func_two $file; done
不直接,不。Find 在一个单独的进程中执行,而不是在你的 shell 中。
创建一个与您的函数执行相同工作的 shell 脚本并找到-exec
它。
作为参考,我使用以下方法避免这种情况:
for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
_script_function_call $i;
done;
在当前脚本文件中获取find的输出,并根据需要迭代输出。我同意接受的答案,但我不想在我的脚本文件之外公开函数。
我会-exec
完全避免使用。使用xargs:
find . -name <script/command you're searching for> | xargs bash -c