递归遍历所有子目录,如果存在具有特定扩展名的文件,则在该文件夹中运行一次命令

递归遍历所有子目录,如果存在具有特定扩展名的文件,则在该文件夹中运行一次命令

我需要递归遍历文件夹的所有子目录。在子目录中,如果有一个扩展名为“.xyz”的文件,那么我需要在该文件夹中运行一次特定命令。

这是我到目前为止所拥有的

recursive() {
  for d in *; do
    if [ -d "$d" ]; then
      (cd -- "$d" && recursive)
    fi
  dir=`pwd`   
  pattern="*.xyz"
file_count=$(find $dir -name $pattern | wc -l)
if [[ $file_count -gt 0 ]]; then
    echo "Match found. Going to execute a command"
    #execute command
fi
  done
}

(cd /target; recursive)

但问题是,当存在匹配项时,“找到匹配项..”消息会在每个文件夹中显示多次。在解决这个问题的同时,有没有更简单的方法来做到这一点?

答案1

你正在重新发明find

尝试这样的事情(使用 GNUfindutils和 GNU sort):

find /target -iname '*.xyz' -printf '%h\000' | sort -z -u | 
  xargs -0 -r -I {} sh -c "cd {} ; yourcommandhere"

打印找到“*.xyz”文件的-printf目录名称 ( ),并以 NUL 字节 ( ) 作为分隔符。 用于消除重复项,然后用于进入每个目录并运行。%h\000sortxargscdyourcommandhere

您还可以编写一个脚本来使用 xargs 运行。例如

find /target -iname '*.xyz' -printf '%h\000' | sort -z -u | 
  xargs -0 -r /path/to/myscript.sh

简单的 myscript.sh 示例:

#!/bin/sh

for d in "$@" ; do
  cd "$d"
  echo "Match found in $d. Going to execute command"
  # execute command
done

如果有许多匹配的目录,第二个版本将明显更快 - 它只需分叉一次 shell(然后迭代每个参数),而不是为每个目录分叉一次 shell。


顺便说一句,这里实际上既不printf需要sort也不xargs需要......但它们确实使阅读和理解正在发生的事情变得更容易。同样重要的是,通过尽早消除重复项(使用 printf 和 sort),它的运行速度比仅使用 bash 快得多,并且消除了在任何给定目录中多次执行命令的风险(相当小)。

这是执行相同操作的另一种方法,无需排序或 xargs:

find /target -iname '*.xyz' -exec bash -c \
    'typeset -A seen
     for f in "$@"; do
       d="$(dirname "$f")";
       if [[ ! -v $seen[$d] ]]; then
         echo "Match found in $d. Going to execute command"
         # Execute command
         seen["$d"]=1
       fi
     done' {} +

这使用 bash ( ) 中的关联数组$seen[]来跟踪已查看和处理的目录。请注意,如果有数千个匹配*.xml文件(足以超过最大命令行长度,以便 bash 脚本分叉多次),那么您的命令可能在任何给定目录中运行多次。

由 find 选项执行的脚本-exec可以是独立脚本,与上面的 xargs 版本一样。

顺便说一句,这里的任何变体都可以轻松执行 awk 或 perl 或任何脚本,而不是 sh 或 bash 脚本。

答案2

find有一个内置标志来打印字符串,这在这里非常有用:

find -iname "*.xyz" -printf "%h\n"打印包含与您的模式匹配的文件的所有目录的名称(%his justfind的神奇语法扩展到文件目录,\n当然是换行符)。

因此,这就是你想要的:

COMMAND='echo'
find `pwd` -iname "*.pdf" -printf "%h\n" | sort -u | while read i; do                                              
    cd "$i" && pwd && $COMMAND
done

这里发生了一些事情。为了只执行一次命令,我们只需使用标志将其通过管道传输sort-u这会删除所有重复的条目。然后我们用 循环遍历所有内容while。另请注意,我使用了find `pwd`,这是一个很好的技巧,可以find输出绝对路径,而不是相对路径,这使我们可以使用cd而不必担心任何相对路径。

编辑:执行此脚本时请小心您的目录名称,因为目录名称包含换行符 ( \n) 甚至\可能会破坏脚本(也许还有其他不常见的字符,但我还没有测试更多)。解决这个问题很困难,我不知道该怎么做,所以我只能建议不要使用这样的目录。

相关内容