& # 39;找到- # 39;Linux中的shell函数

有没有一种方法来获得find来执行我在shell中定义的函数?

例如:

dosomething () {
echo "Doing something with $1"
}
find . -exec dosomething {} \;

其结果是:

find: dosomething: No such file or directory

有没有办法让find-exec看到dosomething?

98351 次浏览

不直接,不。Find在一个单独的进程中执行,而不是在您的shell中执行。

创建一个shell脚本,完成与函数相同的工作,并找到can -exec that。

以这种方式执行函数是不可能的。

为了克服这个问题,你可以把你的函数放在一个shell脚本中,然后从find调用它

# dosomething.sh
dosomething () {
echo "doing something with $1"
}
dosomething $1

现在在find as中使用它:

find . -exec dosomething.sh {} \;

将函数放在一个单独的文件中,并获得find来执行它。

Shell函数在定义它们的Shell内部;find将永远无法看到它们。

因为只有shell知道如何运行shell函数,所以必须运行shell才能运行函数。你还需要用export -f标记你的export函数,否则子shell将不会继承它们:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;
find . | while read file; do dosomething "$file"; done

{}中添加引号,如下所示:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

这个函数纠正了由find返回的特殊字符导致的任何错误, 例如文件名中带有括号的文件

我将完全避免使用-exec。使用xargs:

find . -name <script/command you're searching for> | xargs bash -c

让脚本调用自身,将找到的每个项作为参数传递:

#!/bin/bash


if [ ! $1 == "" ] ; then
echo "doing something with $1"
exit 0
fi


find . -exec $0 {} \;


exit 0

当您单独运行脚本时,它会找到您正在寻找的内容,并调用自己,将每个查找结果作为参数传递。当脚本使用参数运行时,它会执行参数上的命令,然后退出。

江淮的回答是伟大的,但它有几个容易克服的陷阱:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

这将使用null作为分隔符而不是换行符,因此具有换行符的文件名将正常工作。它还使用-r标志,该标志禁止反斜杠转义,如果没有它,文件名中的反斜杠将不起作用。它还清除IFS,这样名字中潜在的尾随空格就不会被丢弃。

批量处理结果

为了提高效率,许多人使用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开始,而且如果find返回的文件名对shell具有特殊含义,则使用$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
}

(但是你需要一个处理-print0find,例如GNU find)。

我发现最简单的方法如下,在一个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

为了对其他一些答案进行补充和澄清,如果你正在使用execexecdir (-exec command {} +)的bulk选项,并且想检索所有的位置参数,你需要考虑用bash -c处理$0

更具体地说,考虑下面的命令,它像上面建议的那样使用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来工作,但同样,你必须记住$@不会像预期的那样工作。

只是一个关于使用shell的接受答案的警告, 尽管它很好地回答了这个问题,但它可能不是在查找结果上执行某些代码的最有效方式:

这里是bash下的所有解决方案的基准测试, 包括一个简单的for循环的情况: (1465个目录,在一个标准硬盘驱动器上,armv7l GNU/Linux synology_armada38x_ds218j)

dosomething() { echo $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”;And "for循环;看起来最好,速度差不多。