教程

grep、sed 和 awk:Linux 终端文本处理示例

更新于: · Skillaria.Top

grep 按模式查找行(grep -rn error /var/log),sed 在文本流或文件中替换内容(sed -i 's/old/new/g' file),awk 按列处理并统计(awk '{print $1}' access.log)。三者通过管道组合,可以替代大量脚本:grep 过滤行,sed 修改文本,awk 提取字段并汇总。

grep、sed 和 awk 是 Linux 中三个经典的文本处理工具。grep 按模式查找行,sed 不必打开编辑器就能替换和删除文本流中的内容,而 awk 可以把每一行拆分成列并进行计算。下面是 grep sed awk 的实用示例,覆盖分析日志、检查配置文件和处理命令输出时真正会用到的场景,并列出常见错误。

什么时候用哪个工具

三个工具都逐行读取文本,并把结果输出到标准输出,因此可以很方便地用 | 连接起来。区别在于每个工具最擅长解决的任务。

任务工具示例
查找包含某段文字的行grepgrep ERROR app.log
替换文字sedsed 's/http:/https:/g' file
按行号显示或删除行sedsed -n '10,20p' file
提取某一列awkawk '{print $1}' access.log
求和、求平均、分组统计awkawk '{s+=$10} END {print s}'

简单的规则:只需要过滤就用 grep;需要修改文本就用 sed;文本中有列或需要算术运算就用 awk。

grep:按模式查找行

grep 基础示例

# 文件中包含 error 一词的行
grep error /var/log/syslog

# 忽略大小写:Error、ERROR、error
grep -i error app.log

# 显示行号
grep -n "Connection refused" app.log

# 递归搜索目录,并显示文件名
grep -rn "DB_HOST" /etc/myapp/

# 只列出有匹配内容的文件名
grep -rl "TODO" src/

# 反向匹配:排除注释和空行之外的所有内容
grep -v -e '^#' -e '^$' /etc/ssh/sshd_config

# 统计匹配行的数量
grep -c " 500 " access.log

参数 -w 按整词匹配:grep -w log 不会匹配到 login。参数 -F 关闭正则表达式,按字面字符串搜索,适用于模式中含有点号、括号或星号的情况。

匹配行的上下文

日志中的错误通常只看一行很难理解,还需要看到引发它的堆栈或请求。为此可以使用上下文参数:

# 匹配行之后的 3 行(After)
grep -A 3 "Traceback" app.log

# 匹配行之前的 2 行(Before)
grep -B 2 "OOM" /var/log/kern.log

# 匹配行前后各 5 行(Context)
grep -C 5 "segfault" /var/log/syslog

grep 用一行 -- 分隔各个匹配组,因此输出很容易阅读。

grep 与正则表达式

grep 默认支持基本正则表达式(BRE)。加上 -E 后启用扩展正则(ERE),此时 +、?、| 和括号无需反斜杠即可使用。

写法含义示例
^行首grep '^root' /etc/passwd
$行尾grep 'bash$' /etc/passwd
.任意单个字符grep 'h.t'
*重复 0 次或多次grep 'ab*c'
+(ERE)重复 1 次或多次grep -E '[0-9]+'
|(ERE)或grep -E 'error|warn'
[...]集合中的任意一个字符grep '[Ee]rror'
# 包含错误或警告的行
grep -E "ERROR|WARN" app.log

# 只提取日志中的 IP 地址,每行一个(-o 表示只输出匹配的部分)
grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log

grep -P:提取两段已知文本之间的值

GNU grep 提供了 -P 参数,支持 Perl 风格的正则。借助 \K(丢弃目前为止匹配的内容)和前瞻断言,可以只提取其中的值:

$ echo 'user_id=4821 status=ok' | grep -oP 'user_id=\K[0-9]+'
4821

$ grep -oP '"version":\s*"\K[^"]+' package.json
2.4.1

并非所有 grep 版本都支持 -P(例如 macOS 自带的 BSD grep 就没有),因此在需要跨平台的脚本中最好使用 sed 或 awk。

脚本条件中的 grep -q

grep -q 不输出任何内容,只返回退出码:0 表示找到,1 表示没找到。这样可以直接把检查写进 if 中:

if grep -q "^PermitRootLogin no" /etc/ssh/sshd_config; then
  echo "root 登录已禁用"
fi

这比把输出保存到变量再判断是否为空更简洁,而且 grep 在第一次匹配后就会停止。

sed:替换与删除文本

替换:命令 s

sed 最常用的命令是 s/模式/替换内容/标志。不加 g 标志时,每行只替换第一处匹配。

# 输出替换后的文本(文件本身不变)
sed 's/foo/bar/' file.txt

# 替换一行中的所有匹配
sed 's/foo/bar/g' file.txt

# 忽略大小写(GNU sed)
sed 's/error/ERROR/gI' app.log

# 文本中含有斜杠时使用其他分隔符
sed 's|/var/www|/srv/www|g' nginx.conf

sed -i:直接修改文件

参数 -i 会把结果写回文件。为了不丢失原文件,可以指定备份后缀:

# 生成 config.ini.bak 备份,并修改 config.ini
sed -i.bak 's/^debug=true/debug=false/' config.ini

# 替换目录中所有 .conf 文件里的内容
find /etc/myapp -name "*.conf" -exec sed -i 's/old-host/new-host/g' {} +

在批量修改之前,先去掉 -i 运行一次,检查输出。在 macOS(BSD sed)上 -i 需要一个必填参数:sed -i '' 's/a/b/' file。

按行号和模式处理行

# 显示第 10–20 行(-n 关闭自动输出,p 表示打印)
sed -n '10,20p' file.txt

# 删除第 5 行
sed '5d' file.txt

# 删除最后一行
sed '$d' file.txt

# 删除空行和注释行
sed -e '/^$/d' -e '/^#/d' nginx.conf

# 只在包含 "listen" 的行中替换
sed '/listen/s/80/8080/' nginx.conf

# 在匹配行之后插入一行(GNU sed)
sed '/^\[mysqld\]/a max_connections = 500' my.cnf

反向引用与 &

括号中的部分模式可以在替换内容中通过 \1、\2 引用;符号 & 表示整个匹配的文本。

# 交换名和姓(通过 -E 使用扩展正则)
$ echo "Иванов Пётр" | sed -E 's/(\S+) (\S+)/\2 \1/'
Пётр Иванов

# 给所有数字加上方括号
$ echo "port 8080, timeout 30" | sed -E 's/[0-9]+/[&]/g'
port [8080], timeout [30]

# 把日期 2026-10-14 转换为 14.10.2026
$ echo 2026-10-14 | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
14.10.2026

awk:列、条件与统计

输出需要的列

awk 以空格和制表符把每一行拆分成字段:$1 是第一个字段,$2 是第二个,$NF 是最后一个,$0 是整行。

# nginx 日志中的 IP 地址(第一列)
awk '{print $1}' /var/log/nginx/access.log

# /etc/passwd 中的用户名和 shell(分隔符为冒号)
awk -F: '{print $1, $7}' /etc/passwd

# 最后一列
df -h | awk '{print $NF}'

# 输出时使用自定义分隔符
awk -F: -v OFS=';' '{print $1, $3}' /etc/passwd

条件:只打印需要的行

可以在花括号块之前写一个条件,awk 只对满足条件的行执行该操作。

# UID 从 1000 开始的用户(普通用户,而非系统用户)
awk -F: '$3 >= 1000 {print $1}' /etc/passwd

# 响应码为 500 及以上的请求(combined 格式中的第 9 个字段)
awk '$9 >= 500' access.log

# 第 6 列匹配正则的行
awk '$6 ~ /POST/' access.log

# ls -l 输出中大于 100 MB 的文件
ls -l | awk '$5 > 100*1024*1024 {print $9}'

BEGIN、END 和 NR:awk 作为小程序

块 BEGIN 在读取第一行之前执行,END 在最后一行之后执行。NR 保存当前行号,在 END 块中它就是总行数。

# 响应大小(第 10 个字段)之和,以兆字节显示
awk '{sum += $10} END {printf "%.1f MB\n", sum/1024/1024}' access.log

# 最后一列的平均响应时间
awk '{s += $NF} END {if (NR) print s/NR}' timings.log

# 跳过 CSV 的表头
awk -F, 'NR > 1 {print $2}' users.csv

# 按 IP 统计请求次数:关联数组
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head

最后一个例子是分析日志的经典写法:以 IP 为键的数组 count 可以替代 sort | uniq -c 的组合,并且很容易扩展,例如计算每个地址的流量总和。

管道:grep、sed 和 awk 组合使用

真正的威力在于组合。每个工具完成自己的部分,| 再把结果传给下一个。

访问 5xx 错误最多的前 10 个 IP

grep ' 50[0-9] ' /var/log/nginx/access.log \
  | awk '{print $1}' \
  | sort | uniq -c | sort -rn | head -10
    412 203.0.113.45
    118 198.51.100.7
     36 192.0.2.19

这里 grep 粗略地筛掉无关行,awk 取出字段,sort | uniq -c 统计重复次数。也可以只用一个 awk 完成:awk '$9 ~ /^5/ {c[$1]++} END {for (i in c) print c[i], i}' access.log | sort -rn | head。这种写法更精确,因为它检查的是响应码所在的那一列。

最常被访问的 URL

awk '{print $7}' access.log | sed 's/?.*//' | sort | uniq -c | sort -rn | head

sed 在这里去掉了查询字符串,使 /search?q=a 和 /search?q=b 被计为同一个页面。

去掉注释后读取配置中的值

grep -v '^\s*#' /etc/myapp/app.env | grep '=' | awk -F= '{print $1}'

某一小时内的错误

grep '14/Oct/2026:03:' access.log | awk '$9 >= 500 {print $4, $7, $9}'

常见错误

  • 在 awk 中使用双引号而不是单引号。 在 awk "{print $1}" 中,shell 会在 awk 启动之前替换掉它自己的变量 $1。awk 程序和 sed 表达式应使用单引号。
  • 忘记 sed 的 g 标志。 没有它,一行中只替换第一处匹配,其余的保持不变。
  • 没有检查就使用 sed -i。 错误的正则会悄无声息地破坏文件。先去掉 -i 运行,或使用 -i.bak。
  • 在 grep 中 + 和 | 不起作用。 这是扩展语法,需要 grep -E(或 sed -E)。
  • 模式中的点号。 grep 192.168.1.1 也会匹配 192x168y1z1,因为点号表示“任意字符”。请使用 grep -F 或写成 \.。
  • 列中含有空格。 如果字段中可能包含空格(CSV、路径),请显式指定分隔符:awk -F, 或 awk -F'\t'。
  • 多余的 cat。 cat file | grep x 也能工作,但 grep x file 更短,并且不会多启动一个进程。

可以用什么替代 grep、sed 和 awk

对于 JSON,这些工具并不方便:结构最好用 jq 解析(jq '.items[] | select(.status=="active") | .name'),YAML 则用 yq。对于大型项目中的快速递归搜索,常用 ripgrep(命令 rg),它默认遵循 .gitignore。如果你更习惯 Perl 语法,perl -pe 's/a/b/g' 和 perl -lane 'print $F[0]' 也能用一行命令替代 sed 和 awk。但 grep、sed 和 awk 几乎在所有服务器上都预装好了,无需安装,因此首先应该掌握它们。

总结

grep 查找行,sed 修改行,awk 处理列并统计。记住这组基本用法:grep -rniE 以及 -A/-B/-C;sed 's/.../.../g' 配合 -i.bak 和 -n 'Np';awk -F 配合 $1、$NF、条件和 END 块。用这些基本组件几乎可以组合出任何日志分析。这些 grep sed awk 示例与 find、jq 和 curl 一起,在 Linux 课程中关于终端文本处理的课时里有实际演示。

常见问题

grep 和 egrep 有什么区别?

egrep 是 grep -E(扩展正则)的旧别名。现代 GNU grep 调用 egrep 时会给出警告,因此应当使用 grep -E。

如何在目录中的所有文件里搜索文本?

使用 grep -rn "文本" /路径:-r 表示递归搜索,-n 显示行号。要限定文件类型,可加上 --include="*.php"。

如何一次替换许多文件中的文本?

把搜索和 sed 结合起来:grep -rl "old" . | xargs sed -i 's/old/new/g'。操作之前最好先提交代码或做好备份。

如何在 awk 中以制表符分隔输出多列?

设置输出分隔符:awk -v OFS='\t' '{print $1, $3}' file。如果不设置 OFS,输出字段之间默认用空格分隔。

如何用 sed 删除行尾的空格?

命令 sed -i 's/[[:space:]]*$//' file 会删除文件每一行末尾的空格和制表符。

通过视频学习本主题

课程“Linux: с нуля до уверенного администрирования”结合实践详细讲解了这一主题。

前往课程