【问题标题】:Recursively loop through files in bash and manipulate each file through a command递归遍历 bash 中的文件并通过命令操作每个文件
【发布时间】:2020-10-24 17:37:26
【问题描述】:

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

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

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

让我们保持简单。说,

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

【问题讨论】:

  • 所以,展示你到目前为止所拥有的东西。 - 而且,“一项任务”通常不仅仅是一个问题。我们不知道您需要什么帮助,所以请具体说明。
  • $ for f in *.jpg ; do convert $f -scale 25% resized/$f ; done
  • @GaurangiS - 你的循环不是递归的。最简单的解决方案是编写一个简短的脚本,将单个文件名作为参数并对其执行所有需要的操作。调用fixfiles 并使其可执行chmod +x fixfiles。现在只需使用 find-exec 选项并在每个文件上调用 fixfiles 脚本,例如在$HOME 中使用fixfiles,您可以使用find . -type f -exec $HOME/fixfiles '{}' \; 对当前目录中的每个文件进行操作,然后将fixfiles 应用于它们。 (将fixfiles 视为find 调用的辅助脚本)
  • 递归,你的意思是你也想让它在子目录中查找文件吗?

标签: bash unix command-line-interface file-manipulation


【解决方案1】:

使用globstar

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

来自Bash manual

globstar

如果设置,文件名扩展上下文中使用的模式“**”将匹配所有文件以及零个或多个目录和子目录。如果模式后跟“/”,则只有目录和子目录匹配。

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

这是基于codeforester的回答here

【讨论】:

  • 哦,太好了。我不知道globstar。不过,这里要小心的一件事是,我认为您最终也会处理目录。
  • @Vitali True。您可以添加一个简单的检查以跳过目录:[[ -d $f ]] && continue
【解决方案2】:

当您说 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 字符(\0 不是路径的有效部分)。 xargs 的 -0 选项告诉它在读取参数而不是 newliens 时使用 \0 作为分隔符。 -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 "$1" ]]; then
     echo "$1 isn't a directory" >&2
     return 1
  fi

  local dirs=("$1")

  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 非常棘手,可能是一个安全漏洞。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-10
    • 2011-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-09
    • 2021-01-16
    • 2023-03-14
    相关资源
    最近更新 更多