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

1 shell find shell-script recursive

我需要递归遍历文件夹的所有子目录。在子目录中,如果有一个扩展名为“.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)
Run Code Online (Sandbox Code Playgroud)

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

cas*_*cas 5

你在重新发明find

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

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

-printf打印目录名(%h)其中“名为* .xyz”的文件被发现,用NUL字节(\000)作为分隔符。 sort用于消除重复,然后xargs用于cd进入每个目录并运行yourcommandhere.

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

find /target -iname '*.xyz' -printf '%h\000' | sort -z -u | 
  xargs -0 -r /path/to/myscript.sh
Run Code Online (Sandbox Code Playgroud)

简单的 myscript.sh 示例:

#!/bin/sh

for d in "$@" ; do
  cd "$d"
  echo "Match found in $d. Going to execute command"
  # execute command
done
Run Code Online (Sandbox Code Playgroud)

如果有许多匹配的目录,第二个版本会快得多——它只需要派生一个 shell 一次(然后迭代每个参数),而不是每个目录派生一个 shell。


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

这是做同样事情的另一种方式,没有 sort 或 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' {} +
Run Code Online (Sandbox Code Playgroud)

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

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

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