在Linux中找到-exec一个shell函数?


185

有没有办法find执行我在Shell中定义的功能?例如:

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

结果是:

find: dosomething: No such file or directory

有没有办法让find-execdosomething

Answers:


262

由于只有外壳程序知道如何运行外壳程序功能,因此必须运行外壳程序才能运行功能。您还需要使用标记要导出的函数export -f,否则子shell将不会继承它们:

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

7
你打我 顺便说一句,您可以将花括号放在引号内,而不是使用$0
暂停,直到另行通知。

20
如果使用find . -exec bash -c 'dosomething "$0"' {} \;它,它将处理文件名中的空格(和其他奇怪的字符)...
Gordon Davisson 2010年

3
@alxndr:使用双引号,反引号,美元符号,一些转义组合等的文件名将失败
Gordon Davisson 2010年

4
还要注意,除非您也将-f导出,否则您的函数可能正在调用的任何函数将不可用。
hraban '16

3
export -f会在bash的一些版本才能正常工作。这不是POSIX,不crossplatforn,/bin/sh将有一个错误与它
РоманКоптев

120
find . | while read file; do dosomething "$file"; done

10
不错的解决方案。不需要导出函数或在转义参数周围弄乱,并且由于它不产生用于执行每个函数的子外壳,因此效率更高。
汤姆(Tom)

19
但是请记住,它将在包含换行符的文件名上中断。
chepner

3
这是更“空壳的”,因为您可以使用全局变量和函数,而不必每次都创建一个全新的壳/环境。在尝试了亚当的方法并遇到各种各样的环境问题后,以辛苦的方式学习了这一方法。此方法也不会破坏所有导出的当前用户的Shell,并且所需的纪律更少。
雷·福斯

比没有子外壳的可接受答案快得多!不错!谢谢!
比尔·海勒

2
我也通过更改while readfor循环来解决我的问题;for item in $(find . ); do some_function "${item}"; done
user535953118年

22

雅克(Jak)的答案很好,但有一些容易克服的陷阱:

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

它使用null作为分隔符而不是换行符,因此带有换行符的文件名将起作用。它还使用-r禁用反斜杠转义的标志,如果没有文件名中的反斜杠将不起作用。它还清除,IFS以使名称中潜在的尾随空白都不会被丢弃。


1
这对()有好处,/bin/bash但不会起作用/bin/sh。真可惜。
РоманКоптев

@РоманКоптев幸运的是,至少它在/ bin / bash中有效。
sdenham

21

{}如下所示添加引号:

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

这可以纠正由于所返回的特殊字符而导致的任何错误find,例如,名称中带有括号的文件。


1
这不是正确的使用方法{}。这将破坏包含双引号的文件名。touch '"; rm -rf .; echo "I deleted all you files, haha。哎呀。
gniourf_gniourf 2014年

是的,这很糟糕。可以通过注入来利用它。非常不安全。不要使用这个!
多米尼克

1
@kdubs:在命令字符串中使用$ 0(不带引号),并将文件名作为第一个参数传递:-exec bash -c 'echo $0' '{}' \;请注意,使用时bash -c,$ 0是第一个参数,而不是脚本名称。
sdenham

10

批量处理结果

为了提高效率,许多人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,而且$0如果findshell 返回的文件名具有特殊含义,则使用可能会导致意外行为。


9

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

#!/bin/bash

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

find . -exec $0 {} \;

exit 0

当您单独运行脚本时,它会查找您要查找的内容,并通过将每个查找结果作为参数进行调用来进行调用。当脚本使用参数运行时,它将在参数上执行命令,然后退出。


好主意,但风格不好:出于两个目的使用相同的脚本。如果要减少bin /中的文件数,则可以将所有脚本合并为一个在开始时具有大写子句的脚本。非常干净的解决方案,不是吗?
user829755

更不用说这将失败,find: ‘myscript.sh’: No such file or directory如果开始作为bash myscript.sh...
Camusensei 17-4-20

5

对于那些正在寻找将对当前目录中的所有文件执行给定命令的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)。


3

我发现最简单的方法如下,在一个命令中重复两个命令

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done

3

作为参考,我避免使用以下情况:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

在当前脚本文件中获取find的输出,并根据需要迭代输出。我确实同意接受的答案,但是我不想在脚本文件之外公开函数。


这有大小限制
理查德(Richard)

2

无法以这种方式执行功能

为了解决这个问题,您可以将函数放在shell脚本中,然后从 find

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

现在在查找中将其用作:

find . -exec dosomething.sh {} \;

试图避免〜/ bin中有更多文件。不过谢谢!
alxndr 2010年

我考虑过投票,但解决方案本身还不错。请使用正确的引号:dosomething $1=> dosomething "$1"并使用find . -exec bash dosomething.sh {} \;
Camusensei '17

2

将函数放在单独的文件中并开始find执行。

Shell函数位于定义它们的Shell内部。find将永远无法看到他们。


陷阱 说得通。试图避免在我的〜/ bin中出现更多文件。
alxndr 2010年

2

要为其他一些答案提供补充和说明,如果您对execexecdir-exec command {} +)使用bulk选项,并且想要检索所有位置参数,则需要考虑$0with 的处理bash -c。更具体地说,考虑下面的命令,该命令bash -c按上面的建议使用,并从找到的每个目录中简单地回显以“ .wav”结尾的文件路径:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

bash手册说:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

在这里,'check $@'是命令字符串,并且_ {}是命令字符串之后的参数。请注意,这$@是bash中的特殊位置参数,可扩展到从1开始的所有位置参数。还要注意,使用该-c选项,第一个参数将分配给position参数$0。这意味着,如果您尝试使用访问所有位置参数$@,则只会获得从头开始的参数$1。这就是为什么Dominik的答案使用_,这是fill参数的伪参数的原因$0,因此$@,例如,如果我们使用参数扩展(例如在该答案中使用for循环),我们想要的所有参数都将在以后可用。

当然,类似于已接受的答案,bash -c 'shell_function $0 $@'也可以通过显式传递来工作$0,但同样,您必须记住这$@不会按预期进行。



-2

我会避免-exec完全使用。为什么不使用xargs

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

当时,IIRC试图减少使用的资源量。考虑找到数百万个空文件并将其删除。
alxndr 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.