递归循环 bash 中的文件并通过命令操作每个文件

Recursively loop through files in bash and manipulate each file through a command

我想递归循环 bash 文件夹中的每个文件,并对它们进行某种简单的操作。比如修改权限,修改时间戳,用ImageMagick调整图片大小等等,你懂的。

我知道(像大多数初学者一样)如何在目录中执行此操作,但是递归...?

$ for f in *.jpg ; do convert $f -scale 25% resized/$f ; done

让我们保持简单。说,

$ for f in *; do touch $f; done

使用globstar:

shopt -s globstar
for f in ./**; do
    touch "$f"
done

来自Bash manual

globstar

If set, the pattern ‘**’ used in a filename expansion context will match all files and zero or more directories and subdirectories. If the pattern is followed by a ‘/’, only directories and subdirectories match.

顺便说一句,always quote your variables, and use ./ in case of filenames that look like options

这是基于codeforester's answer

当您说 bash 时,您的意思是在 bash 内还是仅使用常用命令行工具编写脚本?

一起编写脚本工具

find . -type f -exec chmod u+x {} \;

这意味着当前目录中的每个文件都可以执行。 \;; 传递给 find 命令,该命令将其解释为“分别调用每个找到的路径上的 exec 字符串”。您可以将 \; 替换为 \+,这会告诉 find 首先收集所有路径并一次性将其替换为 {}。通常 \+ 可以更有效,但您必须小心命令行长度,因为有限制。然后你可以做的是将它与 xargs:

结合起来
find . -type f -print0 | xargs -0 -P $(nproc) -I{} chmod u+x {}

它的作用是告诉 find 使用空字符作为终止符而不是换行符。这可确保您正确处理每个条目,即使它具有任意空格或随机 UTF 字符([=20=] 不是路径的有效部分)。 xargs 的 -0 选项告诉它在读取参数而不是 newliens 时使用 [=20=] 作为分隔符。 -P 选项表示 运行 命令并行 N 次,在这种情况下,N 是 nproc 命令的输出,它打印处理器的数量。 -I 是替换字符串,其余是要处理的命令字符串。

findxargs 的手册页值得探索。

本地 Bash

如果您正在寻找一个完全在 Bash 内并且没有外部工具的解决方案,它会稍微复杂一些并且会涉及一些更高级的 Bash 特定语言结构,其中你自己实现。要遍历目录的内容,您可以执行类似 for path in <dir>; do 的操作。然后你会使用内置测试 [[ -d "$path" ]] 来确定它是否是一个目录,[[ -f "$path" ]] 如果它是一个文件等(man test 有很多解释,但请注意这是独立的 test 可执行文件与功能更丰富、更安全的 bash 版本 [[ ]].

有细微差别和缺陷

使用 bash 个数组:https://www.tldp.org/LDP/Bash-Beginners-Guide/html/sect_10_02.html Bash测试介绍:https://www.tldp.org/LDP/abs/html/testconstructs.html

该测试介绍没有提到的是正则表达式之类的东西,它们将成为该语法的一部分。 Bash 还有强大的操作变量内容的选项:https://www.tldp.org/LDP/abs/html/parameter-substitution.html

但在实践中,即使是适度复杂的东西(无论是在 bash 中还是通过组合工具),在 Python 中可能会得到更好的维护并且更容易阅读(作为拥有大量知识的人来说) Bash).

方面的丰富经验
find_files() {
  if [[ ! -x "" ]]; then
     echo " isn't a directory" >&2
     return 1
  fi

  local dirs=("")

  while [[ "${#dirs[@]}" -gt 0 ]]; do
    local dir="${dirs[0]}"
    dirs=("${dirs[@]:1}") # pop the element from above

    # whitespace in filenames iterated will be a problem. Look to the IFS
    # variable to handle those more gracefully.
    for p in "${dir}"/*; do 
      if [[ -d "$p" ]]; then
         dirs+=("$p")
      elif [[ -f "$p" ]]; then
         echo "$p"
      fi
    done
  done
}

for f in $(find_files .); do
    chmod u+x "$f"
done

如您所见,这比仅使用 find/xargs 二进制文件更复杂、更棘手且更慢。在现实中你永远不会想写这样的东西。你甚至可以在将 find_files 转换为 process 的地方变得更有趣,方法是让它接受一个命令,然后在你通过 eval 迭代(而不是回显路径)时对其进行评估。 eval 非常棘手并且可能是一个安全漏洞。