在Linux中查找-exec shell函数?

alx*_*ndr 169 linux bash shell bsd find

有没有办法find执行我在shell中定义的函数?例如:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;
Run Code Online (Sandbox Code Playgroud)

结果是:

find: dosomething: No such file or directory
Run Code Online (Sandbox Code Playgroud)

有没有办法让find-execdosomething

Ada*_*eld 241

由于只有shell知道如何运行shell函数,因此必须运行shell来运行函数.您还需要标记要导出的函数export -f,否则子shell将不会继承它们:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;
Run Code Online (Sandbox Code Playgroud)

  • 如果你使用`find.-exec bash -c'dosomething"$ 0"'{} \;`它将处理文件名中的空格(和其他奇怪的字符)... (17认同)
  • 你打败了我.顺便说一句,你可以把括号放在引号内,而不是使用`$ 0`. (7认同)
  • 另请注意,除非您导出-f那些函数,否则_your function_可能正在调用的任何函数都将无法使用. (4认同)
  • @alxndr:用双引号,反引号,美元符号,一些逃避组合等文件名都会失败...... (3认同)
  • `export -f`只能在某些版本的bash中使用.它不是posix,不是crossplatforn,`/ bin/sh`会有错误 (3认同)
  • @Gordon:怎么样''dosomething'{}"'` (感谢亚当) (2认同)
  • 我认为如果文件名对 shell 具有特殊含义,这可能会中断。它也与从 $1 开始的参数不一致。如果迷你脚本变得稍微复杂一点,这可能会非常混乱。我建议使用 `export -f dosomething; 找 。-exec bash -c 'dosomething "$1"' _ {} \;` 代替。 (2认同)

小智 107

find . | while read file; do dosomething "$file"; done
Run Code Online (Sandbox Code Playgroud)

  • 但请记住,它会破坏包含换行符的文件名. (17认同)
  • 好的解决方案 不需要导出函数或搞乱转义参数,并且可能更高效,因为它不会产生子shell来执行每个函数. (9认同)
  • 这更像是"shell'ish",因为您的全局变量和函数将可用,而不是每次都创建一个全新的shell /环境.在尝试了Adam的方法并遇到各种各样的环境问题之后,学到了很多.此方法也不会损坏您当前用户的shell以及所有导出,并且需要更少的dicipline. (3认同)
  • 我也通过更改for循环的while读取来解决我的问题;对于$(find。)中的项目;做some_function“ $ {item}”; 完成` (2认同)
  • user5359531,这不适用于邪恶的文件名,因为 find 的输出扩展到命令行,因此会受到分词。基本上只有在关键字'in'之后扩展“$@”(或数组元素或下标)才是可靠的,并且双引号必不可少。 (2认同)

小智 18

添加引号{}如下所示:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;
Run Code Online (Sandbox Code Playgroud)

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

  • 这不是使用 `{}` 的正确方法。这将中断包含双引号的文件名。`touch'"; rm -rf .; echo "我删除了你所有的文件,哈哈`。哎呀。 (4认同)
  • 如何显示使用正确的方法{} (3认同)
  • 是的,这非常糟糕。它可以通过注入来利用。很不安全。不要使用这个! (2认同)
  • @sdenham 你应该双引号 $0 以避免分词。但在 Bash 中似乎没有必要引用“{}”。我想这对于某些 shell 来说是必要的,因为它们告诉你在“find”的手册页中引用它。 (2认同)

小智 18

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

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done
Run Code Online (Sandbox Code Playgroud)

这使用null作为分隔符而不是换行符,因此带有换行符的文件名将起作用.它还使用-r禁用反斜杠转义的标志,没有文件名中的反斜杠将无法正常工作.它也会清除,IFS以便不丢弃名称中潜在的尾随空格.

  • 它适用于“/bin/bash”,但不适用于“/bin/sh”。有什么可惜的。 (2认同)

小智 8

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

#!/bin/bash

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

find . -exec $0 {} \;

exit 0
Run Code Online (Sandbox Code Playgroud)

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


Dom*_*nik 8

处理结果大量

为了提高效率,许多人使用xargs大量处理结果,但这是非常危险的.因此,引入了另一种方法find来批量执行结果.

不过请注意,此方法可能附带像例如在POSIX-要求一些注意事项find{}在命令结束.

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +
Run Code Online (Sandbox Code Playgroud)

find将多个结果作为参数传递给单个调用,bash并且for-loop遍历这些参数,dosomething在每个参数上执行函数.

上面的解决方案开始论证$1,这就是为什么有一个_(代表$0).

逐个处理结果

以同样的方式,我认为应该纠正被接受的最佳答案

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;
Run Code Online (Sandbox Code Playgroud)

这不仅更加理智,因为参数应该始终开始$1,但$0如果返回的文件名find对shell具有特殊含义,则使用可能会导致意外行为.


use*_*316 8

只是关于使用 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
Run Code Online (Sandbox Code Playgroud)

“find | while”和“for循环”似乎最好并且速度相似。


Jas*_*ese 5

对于那些正在寻找将对当前目录中的所有文件执行给定命令的 Bash 函数的人,我从上述答案中编译了一个:

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}
Run Code Online (Sandbox Code Playgroud)

请注意,它打破了包含空格的文件名(见下文)。

以这个函数为例:

world(){
    sed -i 's_hello_world_g' "$1"
}
Run Code Online (Sandbox Code Playgroud)

假设我想将当前目录中所有文件中“hello”的所有实例更改为“world”。我会做:

toall world
Run Code Online (Sandbox Code Playgroud)

为了安全使用文件名中的任何符号,请使用:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}
Run Code Online (Sandbox Code Playgroud)

(但您需要find处理-print0例如 GNU find)。