平时在服务器上排查目录层级、梳理项目结构,或者给新人讲某个目录下到底放了什么东西,用得最多的命令不是 ls -R,而是 tree。Linux 命令大全这个系列走到第 7 篇,前面几篇聊过 df、du、fdisk、mount 这些磁盘管理里的老面孔,今天这篇实操的主角是 tree。严格说,tree 并不是传统意义上的磁盘管理工具,它不检查分区、不计算剩余空间,但它能把磁盘上的目录结构像画地图一样铺开,让你一眼看出空间可能被哪个目录吃掉了——很多 du 排查完之后的下一步动作,就是 tree 出来确认目录层级。
这篇写给谁?一个是刚入行的运维和 Python/后端开发,经常要在陌生机器上快速摸清目录结构;另一个是偶尔要写交付文档、给领导展示项目组织方式的人。tree 的入门成本几乎为零,一条命令加几个参数就能解决问题,但它的参数远比大多数人以为的多。这篇文章我从安装、常用参数、真实工作场景、常见坑四个方面来讲,尽量贴近实际操作,而不是把 man tree 抄一遍。
1. 先把 tree 装上:不同发行版的安装差异
很多人第一次执行 tree 会碰到 command not found,这是因为 tree 默认不在最小化安装的软件包里。它不像 ls、cd 这种 shell 内置或 coreutils 自带的命令,需要单独装。
1.1 Debian/Ubuntu 系安装
bash复制apt update && apt install tree -y
装完之后可以验证一下版本:
bash复制tree --version
输出大致是 tree v2.0.2 (c) 1996 - 2022 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro 这样的信息。不同小版本的参数支持略有差异,比如 --du、--filelimit 在旧版本里不一定好使,所以先确认版本是值得的一步。
1.2 CentOS/RHEL 系安装
bash复制yum install tree -y
CentOS 7 的默认源里就有 tree,不需要额外加 EPEL。不过如果你用的是精简版镜像,可能连 yum 源都没配全,这种情况先 yum update 或者检查源是否可用。
1.3 嵌入式环境怎么办
标题相关的热搜词里有不少“嵌入式 Linux”,嵌入式设备上的 busybox 往往不带 tree,为了一个目录展示去编译安装也不现实。我自己的做法是:用 find 模拟层级输出,比如:
bash复制find /usr -maxdepth 3 -print | sed 's;[^/]*/;|____;g;s;____|; |;g'
这条命令的思路是让每个路径都带上前缀,把 / 替换成层级符号。虽然效果没有 tree 精致,但在设备上应急足够了。这个技巧后面讲 find 配合时会再深入说。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心参数逐个拆解:不只看目录,还要看出门道
tree 的默认输出就是递归展示当前目录下的所有目录和文件。但真正干活的时候,很少有人直接裸敲 tree,因为目录一大,输出的内容能淹掉整个屏幕。必须学会用参数来“控制视野”。
2.1 深度控制:-L 与目录层级
-L 是 tree 命令里我用得最频繁的参数,它控制递归的层级深度。比如只想看最外两层:
bash复制tree -L 2
这个参数的价值在于:一个项目根目录下面往往有成百上千个 node_modules 或 vendor 目录,如果不限制深度,终端会被刷爆。我在排查项目目录时习惯先用 tree -L 1 看第一层有什么,再逐步加深到 2、3 层。控制深度本质上就是控制信息量,就像你进一栋楼,先看楼层索引,再决定上几楼,而不是挨个房间推门。
2.2 只显示目录:-d
很多时候我们只关心目录结构,对那些零散的文件没兴趣。比如给一个 JAR 包做目录确认,或者给交接文档截图,文件列表反而干扰视线。
bash复制tree -d
-d 配合 -L 特别好用,堪称目录结构速写神器。我经常用 tree -d -L 2 给新来的同事讲一个项目的整体模块划分,先让他建立起“这个系统分为哪几块”的认知,再具体到每个文件。比甩给人一张满屏文件名的输出友好得多。
上面的操作中,一个很实在的经验是:在写文档、做汇报时,tree -d 输出的目录树可以直接粘贴进 Markdown 或 Confluence,配合代码块展示,结构非常漂亮。
2.3 显示隐藏文件:-a
默认 tree 不会显示点开头的文件,比如 .git、.env、.dockerignore。有时候目录里就藏着猫腻,不显示这些等于漏掉了一半信息。
bash复制tree -a
实际生产中,我排查一些跑不起来的项目时会用 tree -a -L 2 确认 .env 文件是否存在、.git 目录是否完整、CI 配置文件有没有放对位置。这里要给一个提示:
-a不要和-L 3这种深层级参数同时裸用,.git目录内部的层级非常深,会把输出拉得很长,建议配合-d使用,先看看隐藏目录本身,不要深入内部。
2.4 显示完整路径与大小信息:-f、-h、--du
这几个参数放在一起说,因为它们的目标都是让输出信息更“完整”。
-f 会给每个文件都加上完整路径前缀,这在批量操作中非常有用。比如想把 tree 列出的所有 .log 文件传给脚本处理,完整路径能让后续的 xargs、grep 省去拼接路径的麻烦。
bash复制tree -f
-h 是以人类可读的方式显示文件大小,类似 ls -lh,输出成 K、M、G 这种单位。配合 --du 使用才是真正的大杀器:--du 会让 tree 计算每个目录累计占用的磁盘空间,并显示在目录名后面。这在磁盘快满的时候定位“哪个目录是空间杀手”特别好用。
bash复制tree --du -h
注意:--du 计算的是一个目录下所有文件和子目录的总大小,而 -h 控制大小的显示格式。我排查磁盘空间时会先用 df -h 确认哪个分区满了,然后到这个分区里逐级用 tree --du -h -L 2 找出最大的目录,再深入其中进去,这个效率比单纯 du -sh * 要高,而且能看到完整层级,不容易遗漏。
2.5 过滤和排除:-P 与 -I
-P 是通配符匹配模式,只显示匹配的文件;-I 是排除指定的目录或文件。这两个参数是我日常使用率最高的组合。
比如在一个源码目录里,只想看所有 Python 文件和目录,忽略 .git 和 __pycache__:
bash复制tree -P "*.py" -I "__pycache__|.git"
注意 -P 匹配的是文件路径的最后一个部分,所以 -P "*.log" 只匹配以 .log 结尾的文件。这里有个容易踩的坑:-P "*.py" 并不会排除那些目录名以 .py 结尾的目录,比如 mypackage.py 这个目录也会被匹配进来。想要精确控制,需要把规则想清楚,必要时先用 tree -d 确认一下目录名称。
-I 还支持从文件中读取排除规则,但没有直接的参数,通常的做法是把规则写入变量,比如:
bash复制MYSQL_TREE_EXCLUDE=".git|node_modules|vendor"
tree -I "$MYSQL_TREE_EXCLUDE"
2.6 排序展示:--dirsfirst 与 --sort
默认情况下,tree 按文件名排序,目录和文件混合在一起。--dirsfirst 让目录优先显示,然后是文件,其实更符合人的阅读习惯。
bash复制tree --dirsfirst
另一个好用的排序参数是 --sort=size,按文件大小排序。配合 -h,找“目录里最大的文件是哪个”时很有用。比如定位某个日志目录里哪个文件最占空间:
bash复制tree /var/log -h --sort=size
2.7 剪枝与文件数控制:--prune、--filelimit
--prune 是剪掉空目录。有时候目录树里到处都是空文件夹,影响判断,加上这个参数能让输出干净得多。注意 --prune 只剪掉“空目录”,如果一个目录下有文件但没有子目录,它会保留。
--filelimit 是一个容易被忽略但实际很有用的参数。它接收一个数字,比如 --filelimit 50,含义是:如果某个目录下的文件数超过 50,则 tree 不会再往下展开这个目录,而是在目录名末尾加上一个 [...] 标记。这个参数在检查大目录时是保命级别的,比如你站在 / 下想跑 tree,不小心就能刷出几万行,有了 --filelimit 可以避免输出爆炸。
bash复制tree / -L 3 --filelimit 20
这里的 -L 3 本身已经限制了深度,再加上 --filelimit 等于双保险。建议排查系统大目录时,永远让 --filelimit 和 -L 同时出现。
2.8 时间显示:--timefmt 与 -D
-D 可以显示每个文件的最后修改时间,--timefmt 设置时间格式。这个功能在按时间定位问题时有用,虽然不如 find -newer 精确,但胜在直观。
bash复制tree -D --timefmt "%Y-%m-%d %H:%M"
注意:--timefmt 必须配合 -D 使用,单独写 --timefmt 是不生效的。这个参数组合我在整理交付包时经常用,一眼就能看出哪些二进制文件是最近编译的,哪些配置文件是很久以前的老古董。
3. 真实场景实战:站在工作里用 tree 解决实际问题
参数单独看都很简单,关键是组合起来解决现实问题。下面写三个我在工作中实际落到过的场景。
3.1 场景一:磁盘满了,定位空间被谁吃掉
接到告警说某个服务器 /home 分区使用率达到 95%,用 df -h 确认后,进入 /home,先看一层结构:
bash复制tree /home -L 1 --du -h
输出大概是这样:
code复制/home
|-- [ 12K] admin
|-- [3.1G] deploy
|-- [ 48M] www
|-- [1.8G] backup
一眼锁定 deploy 和 backup,然后深入:
bash复制tree /home/deploy --du -h -L 2 --filelimit 20
有时候问题不是单个大文件,而是某个目录下积累了几十万个小文件,--du 看不出来,--filelimit 却能暴露“这里文件数量异常”。我遇到过一个小型日志服务把大量小文件写进 /tmp/logs,文件数多到 ls 都卡,当时就是用 tree --filelimit 100 快速发现了异常节点,再用 find 去做批量清理。
3.2 场景二:给项目写 README 或交付文档
做交付的时候,文档里最有价值的一张图就是目录结构。我一般是先 tree -d 拿到纯目录树,然后粘到 README 的代码块里,再逐层加注释。但默认输出里没有缩进用的空格对齐,各个发行版的终端字体不一致,粘贴进 Markdown 后可能出现对不齐的情况。一种更稳的做法是利用 -J 输出 JSON,再写个小脚本转成 Markdown 列表。不过实际项目里大部分时候直接粘贴就够了,因为 tree 输出的字符画在等宽字体下几乎不会歪。
如果目录太长,可以先剪掉不需要的部分:
bash复制tree -d -L 4 -I "node_modules|dist|build|.git|__pycache__" --prune
这里 -L 4 控制深度,-I 排除无关干扰,--prune 去掉空目录,最终得到的就是一个清爽的“文档版目录树”。
3.3 场景三:部署前确认 Nginx 站点目录
新接手一个服务器,要部署 Nginx 静态站点,往往得先搞清 html 目录底下到底有什么。用:
bash复制tree /var/www/html -a -L 3 --filelimit 30
隐藏文件也显示出来,避免漏掉 .htaccess 或者某些隐藏的索引文件;文件数量限制在 30 个以内,防止某个子目录爆出几千个静态资源刷屏。这一步确认完毕后,再决定是清空目录重来,还是增量替换,心里就会非常有底。配合前面系列讲的 du 一起用,还能算出当前静态资源到底占了多大空间,方便预估是否要扩容磁盘。
4. 与 find、ls 配合的高级玩法:tree 不是终点,而是起点
tree 本身不是万能的,它擅长“看”,不太擅长“找”和“改”。真正高效的用法是先用 tree 定位目标,再把结果交给 find、grep、xargs 去做后续动作。
4.1 tree 输出转给脚本处理
tree 支持把输出写到文件:
bash复制tree -a -L 3 > /tmp/tree_structure.txt
这个文件可以直接作为交付物附录发给别人看。但更高级的是输出成 JSON:
bash复制tree -J -L 3 > structure.json
-J 生成 JSON 格式,结构是嵌套的,可以用 jq 解析。比如我只想提取所有目录名:
bash复制jq -r '.. | .name? // empty' structure.json
说实话,在真实运维中这一步不一定常用,但如果你的环境里有自动化巡检脚本,这个 JSON 格式就很有用。比如我写过一个小脚本,每晚自动跑一次 tree -J /data/project 然后把 JSON 解析后和 Git 分支的目录结构做比对,用来发现“有哪些目录只在服务器上存在,却没进版本库”。
4.2 tree 和 find 的分工
tree 解决的是“结构呈现”,find 解决的是“条件检索”。两者最常见的配合是:先用 tree -d 确定候选目录,再用 find 带 -name、-size、-mtime 条件去找具体文件。
比如定位日志文件,先 tree -d /var/log -L 2 看清楚有哪些子目录,然后:
bash复制find /var/log -name "*.log" -mtime +30 -size +100M
找到之后该删的删,该打包的打包。这一步如果一上来就 find / -name "*.log",可能要在全盘扫很久,效率很低。先 tree 缩小范围,再 find 精确打击,整个过程相当于先看地图再走路,盲目搜索是性能和时间上的双重浪费。
4.3 用 tree 构造目录骨架
这个玩法可能很多人没想过:tree 能输出目录树,但同时也能借助它的思路来“建”目录树。实际操作中,我一般把目录树清单存为文本,然后用 while read 配合 mkdir -p 来重建:
bash复制tree -d -i > dirs.txt
while read d; do
mkdir -p "/tmp/recreate/$d"
done < dirs.txt
-i 参数的作用是不显示缩进线,只输出纯目录路径,这样每一行就是一个标准路径。这个技巧我刚知道时觉得有点绕,但实际在做项目迁移时非常管用:把生产环境的目录骨架复制到新机器,先建好空目录再同步文件,能避免“同步成功后才发现少建了目录”的窘境。
5. 常见问题与坑:这些细节文档里未必写得明白
tree 用起来不算复杂,但真上手了,有几个问题踩过一次就会记住。
5.1 中文文件名乱码或显示异常
如果在终端里看到中文文件名变成 ? 或者乱码,一般是终端的字符编码与文件名编码不一致。多数现代 Linux 系统默认 UTF-8,乱码往往是 SSH 客户端的问题,比如 Xshell 或 SecureCRT 的会话编码没设成 UTF-8。解决办法是改客户端编码设置,而不是改服务器。服务器上可以确认一下:
bash复制locale
如果 LANG 不是 en_US.UTF-8 或者 zh_CN.UTF-8,可以在 /etc/environment 里加 LANG=en_US.UTF-8,然后 source /etc/environment 或者重启会话。这个问题在嵌入式设备上更常见,因为 busybox 和精简系统的 locale 往往不完整。
5.2 为什么输出没有颜色
tree 默认在有终端的情况下会显示颜色,但如果你把它重定向到文件,或者通过管道传给其他命令,颜色就会消失,因为 tree 自己会检测是否为 TTY。如果想强制带颜色输出到管道(比如配合 less 查看),可以加 -C:
bash复制tree -C | less -R
-R 告诉 less 保留 ANSI 颜色转义符。这个组合在目录层级多、屏幕滚动太快时很实用。注意带颜色的输出重定向到文件后,文件里会有很多转义序列,要给别人看的话建议 -n 关闭颜色,或者直接不加 -C。
5.3 -P 通配符匹配不到文件
这是常见误用。-P "*.log" 是能用的,但 -P "*.log*" 这种模糊写法可能把 .log.20240101 这类文件也匹配进来,导致结果超出预期。还有一点容易忽略:-P 匹配的是文件的 basename,而不是完整路径,所以 -P "conf/*.conf" 这种带路径的写法是不生效的。想实现路径级过滤,建议先 tree -d 排除掉不关心的目录,再用 -P 匹配具体文件类型。
5.4 权限问题导致目录显示不全
如果非 root 用户执行 tree,遇到没有读取权限的目录时,它会输出类似 [error opening dir] 的提示。解决方式很简单,要么使用 sudo,要么明确知道自己看的是“权限允许范围内的目录结构”。我排查生产环境时一般会先 sudo tree -L 2,确保不遗漏。但要注意 root 用户看到的目录树里会包含很多系统目录,输出量大,必须配合 -L 和 -I 过滤。
5.5 tree 在脚本里卡住
有次我在脚本里直接 tree /home,结果那个目录下面挂了一个远程文件系统(NFS 挂载),NFS 服务端响应超时,tree 直接卡了好几分钟。之后我就给自己立了个规矩:在脚本和自动化场景中,凡是涉及路径遍历,要么加 -L 限制深度,要么先确认是否有远程挂载。排查挂载点用 mount | grep,或者 df -hT 看文件系统类型,确认没有 NFS、CIFS 之类的再跑 tree。
6. 一些不太常见但有用的冷门参数
前面讲的都是高频用法,这里再写几个可能一年用不上一次、但遇到特殊情况真的救命的参数。
6.1 --inode 与 --device
tree --inode 会显示每个文件的 inode 编号,--device 显示所在设备编号。在排查硬链接、跨文件系统目录时有用。比如你怀疑某个目录下有很多硬链接指向同一个 inode,可以:
bash复制tree --inode
如果大量文件的 inode 编号相同,基本就能确认是硬链接。这个排查方式比 ls -i 直观,因为整个目录树的结构是摊开的,相同的 inode 一眼就能扫出来。
6.2 -q 与 -N
-q 会把文件名中不可打印的字符替换成 ?,-N 则原样显示非 ASCII 字符。这俩在乱码环境下也经常救急。-N 与 -q 正好相反,是“不要替我把特殊字符换掉”。如果你的文件名里有换行符这种极端情况,显示出来会非常诡异,用 -q 反而能快速看清文件名到底是什么结构。
6.3 --noreport
tree 默认在输出的最后一行会显示类似 3 directories, 5 files 的统计信息。有时候这条信息无关紧要,甚至会影响脚本解析。加 --noreport 可以关掉统计输出,让输出纯净化。
bash复制tree -d --noreport
在脚本中如果要把 tree 的输出和其他命令统一处理,建议始终加 --noreport,避免末尾的统计行混进数据流。
6.4 通过别名固化自己的参数习惯
每个人侧重不同,我会在 ~/.bashrc 里固化几条 alias:
bash复制alias tree='tree -a -I ".git|node_modules|__pycache__|.venv" --dirsfirst'
alias treed='tree -d -L 2'
alias treedu='tree --du -h -L 1'
这样平时敲 tree 就已经带上了排除规则,不用每次手动敲一长串。注意 alias 设定后要 source ~/.bashrc 才生效。有一点需要提醒:如果你固化的 alias 里带了 -a,那点开头的隐藏目录也会显示,有时候会意外带出 .git 内部结构,所以我通常把 .git 放进排除列表。这也是我踩过坑之后改的:一开始只排除了 node_modules,结果 tree 输出里 .git/objects 这种目录也出来了,几千行直接把终端刷爆。
7. 最后的经验之谈
如果你只是偶尔用一下 tree,记住 -L、-d、-I 和 --du 这几个参数基本就够吃遍天了。在服务器上操作时,我的习惯永远是用 -L 压住输出深度,再配合 -I 排除大目录。宁可先看一层,再逐步深入,也不要一次把整个目录树全拉出来——很多生产事故级别的输出爆炸,源头就是这么一条没加参数的 tree 命令。
我自己在实际操作中还有一个习惯:遇到陌生服务器,第一件事是跑 tree -L 1 -a /home 或者 tree -L 1 -a /opt,先把用户目录或者部署目录下有哪些东西摸个底。账号是新的、服务是别人部署的,想快速理解这台机器的“布局”,tree 是性价比最高的起点。如果你在嵌入式环境里没有 tree,用前面说的 find 替换方案应急,也能顶一顶。
要说 tree 的不足之处,主要是它毕竟是个展示工具,对超大目录(几十万文件)的处理效率不如 find 和 du。所以在自动化巡检脚本里,我更倾向于用 find 配合 awk 做统计,只有在人工排查、写文档、做展示时才对 tree 情有独钟。工具无所谓高低,场景用对了就是好工具。下一篇文章打算聊一聊磁盘管理中另一个被低估的命令 df 的隐藏玩法,或者如果你有想看的命令也可以在评论区留言,我优先挑高频的写。
