更改目录并自动执行命令,然后将目录更改回来

更改目录并自动执行命令,然后将目录更改回来

我正在尝试编写一个脚本,该脚本将在具有许多单级子目录的给定目录中运行。该脚本将cd进入每个子目录,对目录中的文件执行命令,然后cd退出并继续进入下一个目录。

输出应返回到与原始目录具有相同结构和名称的新目录。做这个的最好方式是什么?

答案1

最好的方法是使用子shell:

for dir in */; do
  (cd -- "$dir" && some command there)
done

避免以下情况:

for dir in */; do
  cd -- "$dir" || continue
  some command there
  cd ..
done

或者:

here=$PWD
for dir in */; do
  cd -- "$dir" || continue
  some command here
  cd "$here"
done

因为它们不太可靠,特别是当涉及符号链接或运行脚本时当前目录的路径可能会更改时。

不涉及子 shell 的“正确”方法是使用 close-on-exec 标志打开文件描述符上的当前目录,chdir()进入子目录,然后使用fchdir(fd).然而,据我所知,没有任何 shell 对此有任何支持。

不过你可以这样做perl

#! /usr/bin/perl

opendir DIR, "." or die "opendir: $!";
while (readdir DIR) {
  if (chdir($_)) {
    do-something-there;
    chdir DIR || die "fchdir: $!";
  }
}
closedir DIR

答案2

使用 for 循环

要迭代目录的子目录,可以使用for循环。

for dir in */; do
  echo "Doing something in $dir"
done

通配符*/扩展到当前目录中的子目录以及目录的符号链接。名称以 开头的目录.(即,点文件的目录)将被省略。如果您想跳过符号链接,可以明确地执行此操作。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  echo "Doing something in $dir"
done

要对目录中的所有文件执行命令,请使用*通配符。再次强调,这不包括点文件。如果目录为空,则不扩展通配符。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  if (set -- "$dir"*; [ "$#" -ne 0 ]); then
    somecommand -- "$dir"*
  fi
done

如果需要为每个文件单独调用该命令,请使用嵌套循环。在下面的代码片段中,我用来[ -e "$file" ] || [ -L "$file" ]测试该文件是否是现有文件(不包括损坏的符号链接)或符号链接(损坏与否);仅当"$file"由于目录为空而导致未扩展通配符模式时,此条件才会失败。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  for file in "$dir"*; do
    if [ -e "$file" ] || [ -L "$file" ]; then
      somecommand -- "$file"
    fi
  done
done

如果命令需要将子目录作为当前目录,有两种方法可以实现。cd之前和之后都可以打电话。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  if cd -- "$dir"; then
    for file in *; do
      if [ -e "$file" ] || [ -L "$file" ]; then
        somecommand -- "$file"
      fi
    done
    cd ..
  fi
done

这样做的缺点是,在边缘情况下可能会失败,例如在命令执行期间移动子目录或进程无权更改到父目录。为了避免这些边缘情况,另一种方法cd ..是在子 shell 中运行该命令。子 shell 复制原始 shell 的状态,包括其当前目录,并且子 shell 中发生的情况不会影响原始 shell 进程。

for dir in */; do
  if [ -L "${dir%/}" ]; then continue; fi
  ( cd -- "$dir" &&
    for file in *; do
      if [ -e "$file" ] || [ -L "$file" ]; then
        somecommand -- "$file"
      fi
    done
  )
  fi
done

与查找

正如您所看到的,处理非名义案例并不那么容易。其中一些是您可能不必担心的边缘情况,但您应该能够处理空目录。如果您使用 ksh、bash 或 zsh,有更简单的方法来处理这些情况;上面我展示了在普通 sh 中工作的代码(并且不处理点文件)。

枚举文件和目录的另一种方法是find命令。它旨在递归遍历目录树,但即使没有子目录也可以使用它。使用 GNU 或 FreeBSD find,即在非嵌入式 Linux、Cygwin、FreeBSD 或 OSX 上,有一种简单的方法可以对目录树中的所有文件执行命令:

find . ! -type d -execdir somecommand {} \;

这将对所有非目录的文件执行该命令。仅在常规文件上执行它(不包括符号链接和其他特殊类型的文件):

find . -type f -execdir somecommand {} \;

如果要排除直接位于顶级目录中的文件:

find . -mindepth 2 -type f -execdir somecommand {} \;

如果存在子子目录并且您不想递归到它们:

find . -mindepth 2 -maxdepth 3 -type f -execdir somecommand {} \;

相关内容