阶段三 · 文件与系统命令

文件查看与搜索

1. cat: 输出文件全部内容

cat 取自 concatenate(连接). 它的本职是把多个文件的内容连接起来输出,但日常中 90% 的用法是快速看一眼某个文件的内容.

1.1 基础用法

$ cat file.txt                  # 输出单个文件
$ cat a.txt b.txt > combined.txt # 这才是 cat 的本职:连接多个文件
$ cat > newfile.txt # 从键盘读取输入,写入文件(Ctrl+D 结束)
Ctrl+D
$ cat << EOF > config.yaml # heredoc:多行文本直接写入文件
key: value
port: 8080
EOF

什么时候不该用 cat: 文件很大(几百 MB)时,cat 会把整个文件内容倾倒到终端,屏幕疯狂滚动. 这时应该用 less.

1.2 常用选项

选项 含义 适用场景
-n 显示行号(含空行) 代码审查,讨论时引用行号
-b 显示行号(跳过空行) 只看有内容的行的编号
-s 压缩连续空行(squeeze) 日志文件有大量空行时
-A 显示所有不可见字符 排查文件中的怪字符(\r, \t, 行尾空格)
-E 在每行末尾显示 $ 排查行尾空格(只显示行尾标记)
-T Tab 显示为 ^I 区分 Tab 和空格
# cat -A 是最强力的 "照妖镜"--让所有隐藏字符无所遁形
$ echo -e "hello\tworld\r\nline2 " | cat -A
hello^Iworld^M$
line2 $
# ↑Tab ↑\r ↑\n ↑行尾空格

cat 的现代替代: bat

bat(brew install bat)是一个带语法高亮, 行号, Git 变更标记的 cat 增强版. 日常看代码用 bat,管道中传输数据用 cat. alias cat=bat 是很多人的第一行配置.

1.3 输入输出重定向速览

cat 的几个例子中出现了 >, << 等符号. 这些是 Shell 的重定向操作符--控制命令的 stdin(输入)和 stdout/stderr(输出)从哪里来, 到哪里去. 下面是完整的操作符列表:

操作符 方向 作用 例子
cmd > file stdout → 文件 覆盖写入(文件原有内容被清空) echo hello > a.txt
cmd >> file stdout → 文件 追加写入(保留原有内容) echo world >> a.txt
cmd < file 文件 → stdin 从文件读取输入 wc -l < data.txt
cmd << MARKER 多行文本 → stdin heredoc: 把后续多行直到 MARKER 作为输入 cat << EOF > config.yaml
cmd <<< "string" 字符串 → stdin herestring: 把单行字符串作为输入 grep foo <<< "hello foo bar"
cmd 2> file stderr → 文件 把错误输出写入文件 ls /nope 2> errors.log
cmd &> file stdout + stderr → 文件 标准输出和错误输出都写入同一个文件 npm install &> build.log

几个具体例子来感受区别:

# > 覆盖 vs >> 追加
$ echo "第一行" > file.txt # file.txt 内容:第一行
$ echo "第二行" > file.txt # file.txt 内容:第二行(第一行被覆盖了!)
$ echo "第一行" > file.txt # 重新开始
$ echo "第二行" >> file.txt # file.txt 内容:第一行\n第二行(追加)

# << heredoc:多行文本
$ cat << EOF > config.yaml
key: value
port: 8080
EOF
# EOF 不是关键字,只是约定俗成的标记--换成 END,STOP 或任意词都行

# << 'EOF'(引号包围标记)= 关闭变量展开
$ NAME=yutong
$ cat << EOF
Hello $NAME
EOF
Hello yutong ← $NAME 被展开了
$ cat << 'EOF'
Hello $NAME
EOF
Hello $NAME ← 原样输出

# <<< herestring:把单行字符串直接送给命令的 stdin
$ wc -w <<< "hello world foo"
3

# 2> 只捕获 stderr,> 只捕获 stdout
$ ls existing.txt nope.txt > out.log 2> err.log
# out.log = "existing.txt"(stdout 进去了)
# err.log = "ls: nope.txt: No such file or directory"(stderr 进去了)

更详细的管道与重定向将在第 10 篇展开.

2. less: 分页浏览(主力文件查看器)

less 是最该熟练掌握的命令之一. 它把文件内容分页显示,让人前后翻页, 搜索, 跳转--全部通过键盘完成.

名字的由来: Unix 最早的分页器叫 more,只能向前翻. 后来有人写了一个能向后翻的增强版,讽刺地取名 less("less is more"--少即是多).

2.1 导航快捷键

按键 行为 记忆
j / 向下滚动一行 vim 同款
k / 向上滚动一行 vim 同款
Space / f 向下翻一屏(forward) forward
b 向上翻一屏(backward) backward
d 向下翻半屏 half-down
u 向上翻半屏(undo-like) half-up
g 跳到文件开头 go to start
G 跳到文件末尾 Go to end
50g / 50G 跳到第 50 行 数字 + g/G
50p / 50% 跳到文件的 50% 位置 percent
q 退出 quit

2.2 搜索

按键 行为
/pattern 向下搜索 pattern(Enter 确认)
?pattern 向上搜索 pattern
n 跳转到下一个匹配(next)
N 跳转到上一个匹配(Previous)
&pattern 只显示包含 pattern 的行(相当于 grep)

/ 搜索支持正则表达式,且搜索结果会高亮显示. 按 ESC u 可以关闭当前搜索的高亮.

2.3 常用命令行选项

选项 含义 适用场景
-N 显示行号 讨论日志时用行号精确引用
-S 不自动换行(chop long lines) 看日志/CSV/JSON 时水平滚动
-R 保留 ANSI 颜色代码 看带颜色的命令输出(`grep --color
-F 内容少于一屏时自动退出 模拟 cat 的行为(less -F = 智能 cat)
-i 搜索时忽略大小写 不知道确切大小写时
+F 启动后立即进入 follow 模式 相当于 tail -f 但可以用 less 的导航功能
+50 打开文件后直接跳到第 50 行 精确定位
# 日常组合:行号 + 不折行 + 保留颜色
$ grep --color=always ERROR app.log | less -NSR

# 智能 cat:短文件自动退出,长文件进入分页
$ less -F /etc/hosts

# 实时看日志(比 tail -f 更好--因为可以随时 / 搜索)
$ less +F /var/log/system.log

less 之于文本文件,就像 PDF 阅读器之于 PDF.
可以翻页, 搜索, 跳转, 缩放(折行/不折行),但它不修改文件内容.
h 可以看 less 的完整帮助--less 的功能远比这里列的更多.

3. head 与 tail: 看头看尾

3.1 head: 输出文件的前几行/字节

$ head file.txt                # 默认前 10 行
$ head -n 20 file.txt # 前 20 行
$ head -n -5 file.txt # 除最后 5 行外的全部行
$ head -c 100 file.txt # 前 100 个字节
$ head -c 1K file.txt # 前 1KB(支持 K/M/G 后缀)

实际中常与管道配合:

$ ls -lt | head -5             # 只看最近修改的 5 个文件
$ ps aux | sort -rnk4 | head # 看内存占用最高的进程

3.2 tail: 输出文件的后几行/字节

$ tail file.txt                # 默认最后 10 行
$ tail -n 50 file.txt # 最后 50 行
$ tail -n +10 file.txt # 从第 10 行开始到末尾(跳过前 9 行)
$ tail -c 1K file.txt # 最后 1KB

3.3 tail -f: 实时追踪文件增长

tail -f(follow)是运维和开发中最常用的调试手段--实时查看日志文件的追加内容.

$ tail -f app.log               # 追踪文件:文件有新内容立刻显示
# Ctrl+C 退出

$ tail -f app.log error.log # 同时追踪多个文件
# ==> app.log <== # tail 会在每个文件的输出前加文件名头
# ...新内容...
# ==> error.log <==
# ...新内容...

$ tail -F app.log # -F = -f + 文件被删/轮转后自动重连
# 日志轮转(app.log → app.log.1,创建新 app.log)时不会断

tail -f 的原理: 它监听文件的 inode. 当日志轮转工具把 app.log 改名并创建新文件时,-f 还在盯旧的 inode,新的日志就看不到了. -F 会检测文件名是否重新出现并自动切换.

4. find: 文件搜索的瑞士军刀

find 是最强大也最容易被 "只记一个 -name" 的文件搜索工具. 它遍历指定目录,对每个文件逐一检查设定的条件,并可对匹配的文件执行操作.

基本语法: find <搜索路径> <条件> [操作]

macOS 的 find 是 BSD 版本

以下所有选项在 macOS 和 Linux 上都通用. BSD find 和 GNU find 在少数高级选项上有差异(如 -printf 仅 GNU 支持),但日常用到的选项两者完全一致.

4.1 按名称搜索

$ find . -name "*.html"        # 精确匹配(区分大小写)
$ find . -iname "*.HTML" # 忽略大小写
$ find . -name "*.html" -o -name "*.css" # -o = OR,匹配 .html 或 .css
$ find . -name "report-202?-*.pdf" # 通配符匹配
$ find . -not -name "*.txt" # 名字不是 .txt 的文件
$ find . ! -name "*.txt" # 同上,! 是 -not 的简写

通配符必须用引号包起来. 如果不加引号(find . -name *.html),Shell 会先把 *.html 展开为当前目录下的匹配文件,传到 find 时已经不是通配符了.

4.2 按类型搜索

$ find . -type f               # 只找普通文件
$ find . -type d # 只找目录
$ find . -type l # 只找符号链接
$ find /tmp -type s # 只找 socket 文件

4.3 按大小搜索

$ find . -size +100M           # 大于 100MB 的文件
$ find . -size -1k # 小于 1KB 的文件
$ find . -size +100M -size -500M # 100MB 到 500MB 之间
$ find . -size 0 # 空文件(大小恰好为 0)
$ find . -empty # 空文件或空目录(更直观的写法)

大小单位: c 字节, k KB, M MB, G GB. 注意大小写: k 是小写,MG 是大写.

4.4 按时间搜索

时间条件最容易被搞混. 三个时间维度 + 三种比较方式 = 9 个选项:

atime(访问时间) mtime(修改时间) ctime(元数据变更时间)
< N 天 -atime -N -mtime -N -ctime -N
恰好 N 天 -atime N -mtime N -ctime N
> N 天 -atime +N -mtime +N -ctime +N

mtime vs ctime 的区别: mtime 是文件内容被修改的时间,ctime 是文件元数据(权限, 所有者, 链接数)变更的时间. 修改文件内容会同时更新 mtime 和 ctime,但 chmod 只更新 ctime.

更精确的时间控制用 -mmin(分钟):

$ find . -mtime -7              # 最近 7 天内修改的文件
$ find . -mtime +30 # 30 天前修改的文件
$ find . -mmin -60 # 最近 1 小时内修改的文件
$ find . -newer reference.txt # 比 reference.txt 更新的文件

4.5 执行操作: -exec 与 -delete

找到文件后,最常用的两个后续操作:

# -delete:删除找到的文件(比 -exec rm {} 更安全)
$ find . -name "*.tmp" -delete

# -exec:对每个文件执行命令
$ find . -name "*.log" -exec wc -l {} \; # 对每个文件执行 wc -l
$ find . -name "*.jpg" -size +1M -exec convert {} -resize 50% {} \;

# {} 是 "当前匹配到的文件路径" 的占位符
# \; 表示 -exec 参数到此结束(必须写,且反斜杠不能省)

# -exec ... + 比 \; 更高效(批量传参而非每个文件调一次)
$ find . -name "*.txt" -exec grep "ERROR" {} + # 一次传多个文件给 grep

\; vs +: \; 对每个文件执行一次命令(如找到 100 个文件就执行 100 次),+ 把所有文件一次传给命令(类似于 xargs,只执行一次或少数几次). 能用 + 就用 +,效率高得多.

4.6 组合条件

# AND(默认):多个条件之间隐含 AND
$ find . -name "*.html" -size +10k # html 且 大于 10KB

# OR:-o
$ find . -name "*.pdf" -o -name "*.epub" # pdf 或 epub

# 括号分组:需要转义
$ find . \( -name "*.jpg" -o -name "*.png" \) -size +1M
# 含义:(jpg 或 png) 且 大于 1MB

# -not / !:取反
$ find . -type d ! -name "node_modules" # 目录,但名字不是 node_modules

# 限制深度:-maxdepth / -mindepth
$ find . -maxdepth 2 -name "*.html" # 只搜当前目录和下一级
$ find . -mindepth 2 -type f # 只搜二级及更深

find 高频组合速查:

场景 命令
代码库中找某文件 find . -name "Dockerfile"
找最近改的文件 find . -mmin -30 -type f
找大文件清理空间 find . -size +500M -exec ls -lh {} \;
删除所有 .DS_Store find . -name ".DS_Store" -delete
跳过 node_modules find . -not -path "*/node_modules/*" -name "*.js"
统计每种文件数量 find . -type f | sed 's/.*\.//' | sort | uniq -c | sort -rn
批量修改权限 find . -name "*.sh" -exec chmod +x {} +

5. locate: 文件名速查

locatefind 的区别: find 是实时遍历文件系统,locate 是查询预建的数据库. 后者快一个数量级但有延迟.

$ locate nginx.conf            # 瞬间返回结果(从数据库查)
$ locate -i Nginx.conf # 忽略大小写
$ locate -c .html # 只显示匹配数量(count)
$ locate -l 10 .html # 只显示前 10 个结果(limit)
$ locate -r '\.html$' # 正则匹配(以 .html 结尾的文件)

locate 的数据库: macOS 上由 /System/Library/LaunchDaemons/com.apple.locate.plist 管理,定期更新(通常每周一次). 数据库位于 /var/db/locate.database. 可以手动更新:

$ sudo /usr/libexec/locate.updatedb   # macOS 手动更新数据库
find locate
速度 慢(实时遍历磁盘) 极快(查数据库)
实时性 实时 有延迟(数据库快照)
条件丰富度 极丰富(大小, 时间, 权限...) 仅文件名
适用场景 精确搜索, 批量操作 "我记得有个文件叫 xxx,但忘了在哪"

现代替代: fd 和 fzf

fd(brew install fd)是 find 的现代化替代--语法更直观,默认尊重 .gitignore,自带颜色输出. fzf 是模糊搜索,交互式实时过滤. 对于日常开发,这两个工具组合比 find 更高效.

快速回顾

  • cat = 全部输出; cat -A 是照妖镜(显示隐藏字符); 大文件别用 cat,用 less
  • less = 分页浏览器: j/k 上下滚, /pattern 搜索, g/G 跳头尾, q 退出, less +F 相当于 tail -f
  • head -n 20 看前 20 行,head -n -5 去掉最后 5 行
  • tail -n 20 看最后 20 行,tail -f 实时追踪(-F 容忍日志轮转)
  • find 语法: 路径 + 条件 + 操作; -name/-iname 文件名, -type f/d 类型, -size +100M 大小, -mtime -7 时间
  • find -delete-exec ... {} + 执行操作
  • locate 从数据库秒查文件名; find 实时遍历,locate 有延迟但极快

动手练习

  1. 检查隐藏字符: 用 cat -A ~/.zshrc 检查文件中是否有行尾空格或 Tab 混用
  2. less 导航: 用 less -NS 打开一个大文件,练习 /pattern 搜索, n/N 跳转, & 过滤
  3. 实时追踪日志: 用 tail -f 追踪一个正在写入的日志文件(或 tail -f /var/log/system.log 看系统日志)
  4. 按时间找文件: 找出项目目录中最近 30 分钟修改的所有 .html 文件
  5. 找大文件: 找出家目录下大于 100MB 的文件,用人类可读的格式列出
  6. 对比速度: 用 locate zshrc 找系统中所有 zshrc 相关文件,对比 find / -name "*zshrc*" 的速度差异