如何在 Linux 上遍历目录树
已发表: 2022-07-21
Linux 上的目录允许您将文件分组到不同的单独集合中。 缺点是从一个目录移动到另一个目录来执行重复性任务变得乏味。 这是自动化的方法。
所有关于目录
当您被介绍到 Linux 时,您学习的第一个命令可能是ls
,但cd
也不会落后太多。 了解目录以及如何在它们周围移动,尤其是嵌套的子目录,是了解 Linux 如何组织自身以及如何将自己的工作组织到文件、目录和子目录中的基本部分。
掌握目录树的概念——以及如何在它们之间移动——是您在熟悉 Linux 环境时所经历的许多小里程碑之一。 使用带有路径的cd
会将您带到该目录。 像cd ~
或cd
这样的快捷方式可以将您带回您的主目录,然后cd ..
将您在目录树中上移一级。 简单的。
但是,没有一种同样简单的方法可以在目录树的所有目录中运行命令。 我们可以通过不同的方式实现该功能,但没有专门用于该目的的标准 Linux 命令。
某些命令,例如ls
,具有强制它们以递归方式操作的命令行选项,这意味着它们从一个目录开始,并有条不紊地遍历该目录下的整个目录树。 对于ls
,它是-R
(递归)选项。
如果您需要使用不支持递归的命令,则必须自己提供递归功能。 这是如何做到这一点的。
相关:你应该知道的 37 个重要的 Linux 命令
树命令
tree
命令不会帮助我们完成手头的任务,但它确实可以很容易地查看目录树的结构。 它在终端窗口中绘制树,以便我们可以即时了解构成目录树的目录和子目录,以及它们在树中的相对位置。
您需要安装tree
。
在 Ubuntu 上,您需要输入:
sudo apt 安装树
在 Fedora 上,使用:
sudo dnf 安装树
在 Manjaro 上,命令是:
sudo pacman -Sy 树
使用不带参数的tree
会在当前目录下绘制树。
树
您可以在命令行上传递tree
的路径。
树工作
-d
(目录)选项排除文件,只显示目录。
树 -d 工作
这是获得目录树结构清晰视图的最方便的方法。 此处显示的目录树是以下示例中使用的目录树。 有五个文本文件和八个目录。
不要将 ls 的输出解析到遍历目录
您的第一个想法可能是,如果ls
可以递归地遍历目录树,为什么不使用ls
来执行此操作并将输出传递到解析目录并执行某些操作的其他一些命令中呢?
解析ls
的输出被认为是不好的做法。 由于 Linux 能够创建包含各种奇怪字符的文件和目录名称,因此创建通用的、普遍正确的解析器变得非常困难。
您可能永远不会故意创建像这样荒谬的目录名称,但脚本或应用程序中的错误可能会。
解析合法但考虑不周的文件和目录名称很容易出错。 我们可以使用其他方法,它们比依赖解释ls
的输出更安全、更健壮。
使用查找命令
find
命令具有内置的递归功能,它还具有为我们运行命令的能力。 这使我们能够构建强大的单线。 如果它是您将来可能想要使用的东西,您可以将您的单行代码变成别名或 shell 函数。
该命令递归地遍历目录树,查找目录。 每次找到目录时,它都会打印出目录的名称并在该目录中重复搜索。 完成对一个目录的搜索后,它会退出该目录并在其父目录中继续搜索。
找工作 -type d -execdir echo "In:" {} \;
您可以通过列出目录的顺序来查看搜索在树中的进展情况。 通过比较tree
命令的输出与find
one-liner 的输出,您将看到find
如何依次搜索每个目录和子目录,直到找到没有子目录的目录。 然后它返回一个级别并在该级别恢复搜索。
以下是命令的组成方式。
- find :
find
命令。 - work :开始搜索的目录。这可以是路径。
- -type d :我们正在寻找目录。
- -execdir :我们将在找到的每个目录中执行一个命令。
- echo “In:” {} :这是命令。我们只是将目录的名称回显到终端窗口。 “{}”保存当前目录的名称。
- \; : 这是一个用于终止命令的分号。 我们需要用反斜杠转义它,这样 Bash 就不会直接解释它。
稍作改动,我们可以让 find 命令返回匹配搜索线索的文件。 我们需要包含 -name 选项和搜索线索。 在此示例中,我们正在寻找与“*.txt”匹配的文本文件,并将其名称回显到终端窗口。

查找工作 -name "*.txt" -type f -execdir echo "Found:" {} \;
搜索文件还是目录取决于您要实现的目标。 要在每个目录中运行命令,请使用-type d
。 要对每个匹配的文件运行命令,请使用-type f
。
此命令计算起始目录和子目录中所有文本文件中的行数。
查找工作 -name "*.txt" -type f -execdir wc -l {} \;
相关:如何在 Linux 中使用 find 命令
使用脚本遍历目录树
如果您需要遍历脚本中的目录,您可以在脚本中使用find
命令。 如果您需要(或只是想)自己进行递归搜索,您也可以这样做。
#!/bin/bash shopt -s dotglob nullglob 函数递归{ 本地 current_dir dir_or_file 对于 $1 中的 current_dir; 做 echo "目录命令:" $current_dir 对于 "$current_dir"/* 中的 dir_or_file; 做 如果 [[ -d $dir_or_file ]]; 然后 递归“$dir_or_file” 别的 wc $dir_or_file 菲 完毕 完毕 } 递归“$ 1”
将文本复制到编辑器中并将其保存为“recurse.sh”,然后使用chmod
命令使其可执行。
chmod +x 递归.sh
该脚本设置了两个 shell 选项, dotglob
和nullglob
。
dotglob
设置表示以句点“ .
” 将在扩展通配符搜索词时返回。 这实际上意味着我们在搜索结果中包含隐藏文件和目录。
nullglob
设置意味着未找到任何结果的搜索模式将被视为空字符串或空字符串。 他们不默认搜索词本身。 换句话说,如果我们使用星号通配符“ *
”搜索目录中的所有内容,但没有结果,我们将收到空字符串而不是包含星号的字符串。 这可以防止脚本无意中尝试打开名为“*”的目录,或将“*”视为文件名。
接下来,它定义了一个名为recursive
的函数。 这就是有趣的事情发生的地方。
声明了两个变量,称为current_dir
和dir_or_file
。 这些是局部变量,只能在函数内引用。
函数中还使用了一个名为$1
的变量。 这是调用函数时传递给函数的第一个(也是唯一一个)参数。
该脚本使用两个for
循环,一个嵌套在另一个内部。 第一个(外部) for
循环用于两件事。
一种是运行您想要在每个目录中执行的任何命令。 我们在这里所做的只是将目录的名称回显到终端窗口。 您当然可以使用任何命令或命令序列,或者调用另一个脚本函数。
外部 for 循环所做的第二件事是检查它可以找到的所有文件系统对象——可以是文件也可以是目录。 这就是内部for
循环的目的。 反过来,每个文件或目录名称都被传递到dir_or_file
变量中。
然后在 if 语句中测试dir_or_file
变量以查看它是否是目录。
- 如果是,则函数调用自身并将目录名称作为参数传递。
- 如果
dir_or_file
变量不是目录,那么它必须是文件。 您希望应用于文件的任何命令都可以从if
语句的else
子句中调用。 您还可以在同一脚本中调用另一个函数。
脚本中的最后一行调用recursive
函数并传入第一个命令行参数$1
作为要搜索的起始目录。这就是整个过程的开始。
让我们运行脚本。
./recurse.sh 工作
遍历目录,脚本中将在每个目录中运行命令的点由“Directory command for:”行指示。 找到的文件会在其上运行wc
命令以计算行数、单词和字符数。
处理的第一个目录是“work”,然后是树的每个嵌套目录分支。
需要注意的一个有趣的点是,您可以通过将特定于目录的命令从内部 for 循环的上方移动到其下方来更改目录的处理顺序。
让我们将“Directory command for:”行移到内部for
循环done
之后。
#!/bin/bash shopt -s dotglob nullglob 函数递归{ 本地 current_dir dir_or_file 对于 $1 中的 current_dir; 做 对于 "$current_dir"/* 中的 dir_or_file; 做 如果 [[ -d $dir_or_file ]]; 然后 递归“$dir_or_file” 别的 wc $dir_or_file 菲 完毕 echo "目录命令:" $current_dir 完毕 } 递归“$ 1”
现在我们将再次运行该脚本。
./recurse.sh 工作
这一次,这些目录首先从最深的层次应用到它们的命令,备份树的分支。 最后处理作为参数传递给脚本的目录。
如果首先处理更深的目录很重要,那么您可以这样做。
递归很奇怪
这就像用自己的电话给自己打电话,给自己留言,告诉自己下次见面的时候——重复。
在您掌握它的好处之前可能需要付出一些努力,但是当您这样做时,您会发现它是一种解决难题的优雅的编程方式。
相关:什么是编程中的递归,以及如何使用它?