我正在尝试编写一个脚本,该脚本将在具有许多单级子目录的给定目录中运行。该脚本将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 {} \;