【问题标题】:'find -exec' a shell function in Linux'find -exec' Linux 中的一个 shell 函数
【发布时间】:2011-05-18 07:30:33
【问题描述】:

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

例如:

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

结果是:

find: dosomething: No such file or directory

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

【问题讨论】:

    标签: linux bash shell find bsd


    【解决方案1】:

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

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

    【讨论】:

    • 你打败了我。顺便说一句,您可以将大括号放在引号内,而不是使用$0
    • @alxndr:在带有双引号、反引号、美元符号、一些转义组合等的文件名上会失败......
    • 还要注意,你的函数可能调用的任何函数都将不可用,除非你也导出 -f 那些函数。
    • export -f 仅适用于某些版本的 bash。不是 posix,不是 crossplatforn,/bin/sh 会出错
    • 我认为如果文件名对 shell 有特殊含义,这可能会中断。它也与以$1 开头的参数不一致。如果迷你脚本变得有点复杂,这可能会非常混乱。我建议改用export -f dosomething; find . -exec bash -c 'dosomething "$1"' _ {} \;
    【解决方案2】:
    find . | while read file; do dosomething "$file"; done
    

    【讨论】:

    • 不错的解决方案。不需要导出函数或弄乱转义参数,并且可能更有效,因为它不会产生子外壳来执行每个函数。
    • 但请记住,它会破坏包含换行符的文件名。
    • 这更像是“shell'ish”,因为您的全局变量和函数将可用,而无需每次都创建全新的 shell/环境。在尝试了 Adam 的方法并遇到了各种环境问题后,才学会了这一点。此方法也不会破坏您当前用户的所有导出的 shell,并且需要较少的纪律。
    • 我还通过将 while read 更改为 for 循环解决了我的问题; for item in $(find . ); do some_function "${item}"; done
    • user5359531,这不适用于恶意文件名,因为 find 的输出被扩展到命令行,因此会受到分词的影响。在关键字'in'之后扩展“$@”(或数组元素或下标)基本上是可靠的,双引号是必不可少的。
    【解决方案3】:

    Jac's answer 很棒,但它有几个容易克服的陷阱:

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

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

    【讨论】:

    • 这对/bin/bash 有好处,但不适用于/bin/sh。可惜了。
    • @РоманКоптев 多么幸运,至少它可以在 /bin/bash 中工作。
    【解决方案4】:

    {}中添加引号,如下图:

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

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

    【讨论】:

    • 这不是使用{} 的正确方法。这将破坏包含双引号的文件名。 touch '"; rm -rf .; echo "I deleted all you files, haha。哎呀。
    • 是的,这很糟糕。它可以通过注射来利用。很不安全。不要使用这个!
    • @kdubs:在命令字符串中使用 $0(未加引号)并将文件名作为第一个参数传递:-exec bash -c 'echo $0' '{}' \; 请注意,使用 bash -c 时,$0 是第一个参数,而不是脚本名字。
    • @sdenham 你应该双引号 $0 以避免分词。但在 Bash 中,似乎没有必要引用 {}。我想对于某些 shell 来说这是必要的,因为他们告诉你在 find 的手册页中引用它。
    【解决方案5】:

    批量处理结果

    为了提高效率,很多人使用xargs批量处理结果,但是非常危险。因此,find 中引入了一种替代方法,可以批量执行结果。

    请注意,尽管此方法可能带有一些注意事项,例如 POSIX-find 中要求在命令末尾有 {}

    export -f dosomething
    find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +
    

    find 将把许多结果作为参数传递给bash 的单个调用,for 循环遍历这些参数,在每个参数上执行函数 dosomething

    上述解决方案从$1 开始参数,这就是为什么会有_(代表$0)。

    一一处理结果

    同理,我认为接受的最佳答案应该更正为

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

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

    【讨论】:

      【解决方案6】:

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

      #!/bin/bash
      
      if [ ! $1 == "" ] ; then
         echo "doing something with $1"
         exit 0
      fi
      
      find . -exec $0 {} \;
      
      exit 0
      

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

      【讨论】:

      • 很酷的想法,但风格很糟糕:将相同的脚本用于两个目的。如果您想减少 bin/ 中的文件数量,则可以将所有脚本合并为一个在开始时具有大 case 子句的脚本。非常干净的解决方案,不是吗?
      • 更不用说,如果以 bash myscript.sh 开头,这将失败,find: ‘myscript.sh’: No such file or directory...
      【解决方案7】:

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

      【讨论】:

        【解决方案8】:

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

        要克服这个问题,您可以将函数放在 shell 脚本中并从 find 调用它

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

        现在在 find 中使用它:

        find . -exec dosomething.sh {} \;
        

        【讨论】:

        • 试图避免在我的 ~/bin 中添加更多文件。不过谢谢!
        • 我考虑过投反对票,但解决方案本身还不错。请使用正确的引用:dosomething $1 => dosomething "$1" 并使用 find . -exec bash dosomething.sh {} \; 正确启动您的文件
        • 这是正确的做法。真的不用担心 ~/bin 中的其他文件;大概您已经在某个启动文件中定义了dosomething,并且正确维护您的启动文件将使您将它们拆分为不同的文件,因此您最好将该定义放入可执行脚本中。
        【解决方案9】:

        将函数放在一个单独的文件中并让find 执行它。

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

        【讨论】:

        • 明白了;说得通。试图避免在我的 ~/bin 中添加更多文件。
        【解决方案10】:

        为了对其他一些答案进行补充和说明,如果您使用 execexecdir (-exec command {} +) 的批量选项,并且想要检索所有位置参数,则需要考虑处理$0bash -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 的虚拟参数,因此如果我们使用 $@ 参数扩展或 @987654340 等我们想要的所有参数稍后都可用@循环就像那个答案一样。

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

        【讨论】:

          【解决方案11】:

          供您参考, 这是各种解决方案的 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”在速度上似乎最好且相似。

          【讨论】:

            【解决方案12】:

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

            【讨论】:

            • 这包含所有常见的初学者错误,并且会破坏许多不同类型的异常文件名(带有换行符的文件名、带有反斜杠的文件名、带有不规则空格的文件名、带有通配符的文件名等)。
            【解决方案13】:

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

            创建一个与您的函数执行相同工作的 shell 脚本并找到 -exec

            【讨论】:

            • 试图避免在我的 ~/bin 中添加更多文件。不过谢谢!
            【解决方案14】:

            作为参考,我使用以下方法避免这种情况:

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

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

            【讨论】:

            • 这有大小限制
            • 这看起来像是shellcheck.net 的测试用例——简而言之,不要这样做。
            • 如果文件名例如包含空格
            【解决方案15】:

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

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

            【讨论】:

            • 当时,IIRC 试图减少使用的资源量。想想找到数百万个空文件并删除它们。
            猜你喜欢
            • 2015-03-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-11-02
            相关资源
            最近更新 更多