← 全部笔记/我的笔记 2026-10-26 · 约 29 分钟

文本三剑客:grep / sed / awk

更新于 2026-10-26 · 约 11413 字 · 阅读 29 分钟

打开练习题(折叠答案)→
本文目录

一句话记忆:grep 找行、sed 改行、awk 拆行算列。
环境:Rocky Linux / CentOS(GNU 版本工具),示例统一使用 /tmp/lesson 下的练习数据。
学习建议:先看「一、总览」和「二、正则」,再按 grep → sed → awk 顺序过;每节的「短例题」先自己做一遍,再看答案。

零、实验数据准备

后面的例题都基于这两个文件,先照着建好,边看边敲。

mkdir -p /tmp/lesson && cd /tmp/lesson

# 1) 学生成绩表:学号 姓名 语文 数学 英语
cat > students.txt <<'EOF'
1001 张三 90 85 88
1002 李四 78 92 70
1003 王五 59 60 61
1004 赵六 100 95 98
1005 孙七 45 88 79
EOF

# 2) 模拟 nginx 访问日志:IP - - [时间] "请求" 状态码 字节数
cat > access.log <<'EOF'
192.168.1.10 - - [19/Oct/2026:09:01:12 +0800] "GET /index.html HTTP/1.1" 200 1024
192.168.1.11 - - [19/Oct/2026:09:02:33 +0800] "POST /login HTTP/1.1" 302 512
192.168.1.10 - - [19/Oct/2026:09:03:05 +0800] "GET /a.png HTTP/1.1" 404 0
192.168.1.12 - - [19/Oct/2026:09:04:44 +0800] "GET /index.html HTTP/1.1" 200 1024
192.168.1.10 - - [19/Oct/2026:09:05:10 +0800] "GET /admin HTTP/1.1" 403 128
192.168.1.13 - - [19/Oct/2026:09:06:00 +0800] "GET /index.html HTTP/1.1" 200 1024
192.168.1.13 - - [19/Oct/2026:09:07:21 +0800] "POST /login HTTP/1.1" 200 640
192.168.1.11 - - [19/Oct/2026:09:08:59 +0800] "GET /b.css HTTP/1.1" 200 2048
EOF

access.log 的字段位置:$1=IP、$4=日期、$6=GET、$7=路径、$9=状态码、$10=字节数。


一、三剑客总览

1.1 定义(各自是什么)

命令 全称 本质 一句话作用
grep Global Regular Expression Print 行过滤工具 在文件/输入中按行搜索匹配的内容并输出
sed Stream EDitor 流式行编辑器 按规则编辑文本流(替换、删除、插入、打印),可改原文件
awk 三位作者 Aho、Weinberger、Kernighan 的姓氏缩写 文本处理编程语言 按字段(列)处理文本,能计算、判断、统计、格式化输出

三者都诞生于 Unix 早期,都是「读入 → 处理 → 输出」的流式工具,都不会把整个文件一次性读进内存(awk 的数组除外)。

1.2 工作方式对比

grep: 文件/管道 ──逐行读──> 正则匹配? ──是──> 打印该行
                                    └──否──> 丢弃

sed:  文件/管道 ──逐行读──> 模式空间(pattern space) ──命令处理──> 默认打印这一行
                                  ↑ 可执行 s/d/p/a/i/c 等命令,可多轮循环

awk:  文件/管道 ──按记录(行)读──> 自动切成字段 $1..$NF ──执行 {动作} ──> 输出
                                  ↑ 还能 BEGIN/END、条件、循环、数组、函数

1.3 处理单位与能力对比

对比项 grep sed awk
处理单位 行 行(模式空间) 记录(行)+ 字段(列)
默认行为 打印匹配行 处理后打印每一行 执行动作(不写动作则打印整行)
能否取「第 3 列」 不能(-o 只能取匹配串) 不能(只能整行替换) 能,$3
能否做算术/统计 不能 不能 能,求和、平均、计数
能否改原文件 不能 能,sed -i 不能(要重定向/临时文件)
正则类型 默认 BRE,-E 用 ERE,-P 用 PCRE 默认 BRE,-E 用 ERE 默认 ERE(扩展正则,不用转义)
适合场景 搜索、过滤日志、查配置 批量替换、删除行、插入行、改配置 报表、统计、取列、复杂文本加工
学习难度 ★ ★★ ★★★
执行速度 最快 较快 稍慢,但功能最强

二、正则表达式基础(三者通用)

三剑客的核心都是正则,先把这一节看懂,后面全是套公式。

2.1 常用元字符

元字符 含义 示例 匹配结果
. 任意一个字符(换行除外) a.c abc、a-c、a c
* 前一个字符出现 0 次或多次 ab*c ac、abc、abbbc
+ 前一个字符出现 1 次或多次 ab+c abc、abbbc(不含 ac)
? 前一个字符出现 0 次或 1 次 ab?c ac、abc
^ 行首 ^root 以 root 开头的行
$ 行尾 bash$ 以 bash 结尾的行
[] 字符集合,任选一个 [abc]、[0-9] a 或 b 或 c、任意数字
[^] 取反 [^0-9] 非数字字符
() 分组 / 捕获 (ab)+ ab、abab
{n,m} 重复次数 [0-9]{2,4} 2~4 位数字
\< \> / \b 单词边界(GNU) \<root\> 只匹配独立的 root
\ 转义 \. 真正的点号

「或」的写法:竖线 | 表示或。BRE 里要写成反斜杠加竖线(\|),ERE 里直接写竖线。
例:grep 'root\|nobody' /etc/passwd 与 grep -E 'root|nobody' /etc/passwd 等价。

2.2 BRE 与 ERE 的区别(重点,考试/面试常问)

写法 BRE(grep、sed 默认) ERE(grep -E、sed -E、awk 默认)
+ 要写 \+ 直接 +
? 要写 \? 直接 ?
竖线(或) 要写成反斜杠 + 竖线 直接写竖线
() 要写 \( \) 直接 ( )
{} 要写 \{ \} 直接 { }
grep    'ro\+t' /etc/passwd    # BRE:+ 要转义
grep -E 'ro+t'  /etc/passwd    # ERE:不用转义,等价

⚠️ \d、\w、\s 在 grep/sed 默认正则里是不支持的(GNU grep 会把它当成字面量 d、w、s)。
想用请用 POSIX 写法:[0-9]、[[:alnum:]_]、[[:space:]];或者 grep -P '\d+'(PCRE)。

2.3 POSIX 字符类(写在 [] 里)

写法 等价 含义
[[:digit:]] [0-9] 数字
[[:alpha:]] [a-zA-Z] 字母
[[:alnum:]] [a-zA-Z0-9] 字母+数字
[[:upper:]] / [[:lower:]] [A-Z] / [a-z] 大写 / 小写
[[:space:]] 空格、Tab、换行 空白字符
[[:blank:]] 空格、Tab 水平空白
[[:punct:]] 标点 标点符号

2.4 常用的现成正则片段

# IPv4(简化版)
[0-9]{1,3}(\.[0-9]{1,3}){3}

# 邮箱(简化版)
[[:alnum:]._%+-]+@[[:alnum:].-]+\.[[:alpha:]]{2,}

# 纯数字行(含负数)
^-?[0-9]+$

# 空行
^$

# 注释行或空行(配置文件清理用)
^[[:space:]]*(#|$)

三、grep —— 找行(过滤)

3.1 定义

grep 按行读取输入,用正则去匹配,匹配成功的行整行输出(默认)。它只筛选、不修改文件,是最常用的「查找」命令。

名字来源:g/re/p —— globally search a regular expression and print。

3.2 语法结构

grep [选项] '匹配模式' 文件...
命令输出 | grep [选项] '匹配模式'
  • 匹配模式:普通字符串或正则表达式;强烈建议用单引号包起来,防止 shell 提前展开特殊字符。
  • 省略文件名时从标准输入读取(配合管道)。
  • 多个文件时,默认在结果前加上 文件名: 前缀。

3.3 常用选项

选项 作用 记忆
-i 忽略大小写 ignore case
-v 反向选择,输出不匹配的行 invert
-n 显示行号 number
-c 只输出匹配的行数 count
-l 只输出文件名(内容匹配) list
-L 输出不匹配的文件名
-w 匹配整个单词 word
-x 匹配整行(完全相等) exact
-o 只输出匹配到的那部分 only
-E 使用扩展正则(等价 egrep) Extended
-F 按固定字符串匹配,不解析正则(等价 fgrep,速度最快) Fixed
-r / -R 递归搜索目录 recursive
-A n 同时显示匹配行后 n 行 After
-B n 同时显示匹配行前 n 行 Before
-C n 显示匹配行前后各 n 行 Context
-e 指定多个模式(逻辑或)
-f 文件 从文件里读取模式,一行一个
-q 静默模式,不输出,只看返回值 quiet
-m n 最多匹配 n 行后停止 max
-a 把二进制文件当文本处理
-s 抑制「文件不存在」等错误提示
--color=auto 高亮匹配部分(常给 grep 起别名)

3.4 返回值(写脚本必看)

grep -q 'root' /etc/passwd
echo $?    # 0=匹配到了  1=没匹配到  2=命令/文件出错

3.5 短例题与答案

例 1:在 /etc/passwd 中查找 root 用户

grep 'root' /etc/passwd

答案:输出 root:x:0:0:root:/root:/bin/bash 以及 operator:x:11:0:operator:/root:/sbin/nologin 等所有包含 root 的行(注意是包含,不是等于)。

例 2:忽略大小写 + 显示行号

grep -in 'ROOT' /etc/passwd

答案:-i 让 ROOT 也能匹配 root,-n 在行首输出行号,如 1:root:x:0:0:root:/root:/bin/bash。

例 3:统计系统里用 bash 的用户有几个

grep -c 'bash$' /etc/passwd

答案:输出一个数字,如 3。$ 锁定行尾,避免把 /bin/bash 之外的也数进来(一般是 root + 普通用户)。

例 4:找出 /etc/passwd 中不含 nologin 的行(即能登录的用户)

grep -v 'nologin' /etc/passwd

答案:输出所有 shell 不是 /sbin/nologin 的行,一般只剩 root 和你自己创建的用户。

例 5:整词匹配 -w 与普通匹配的区别

grep -w 'root' /etc/passwd

答案:只匹配独立的单词 root。像 operator 这种「包含 root 字母但不是独立单词」的行不会出现,结果比例 1 少。

例 6:同时匹配多个关键字(或关系)

grep -E '^(root|nobody|sshd):' /etc/passwd

答案:输出 root、nobody、sshd 这三个用户的行。^ 保证从行首开始匹配用户名,避免匹配到描述字段里的同名字符串。

例 7:取日志中所有状态码为 404 的请求

grep ' 404 ' /tmp/lesson/access.log

答案:
192.168.1.10 - - [19/Oct/2026:09:03:05 +0800] "GET /a.png HTTP/1.1" 404 0
两侧加空格是为了精确匹配状态码字段,避免匹配到字节数里的 404。

例 8:只提取出 IP 地址,并统计访问次数(高频面试题)

grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' /tmp/lesson/access.log | sort | uniq -c | sort -rn

答案:

text 3 192.168.1.10 2 192.168.1.11 2 192.168.1.13 1 192.168.1.12

-o 只输出匹配的部分,uniq -c 计数,sort -rn 按次数倒序。

例 9:显示匹配行前后各 1 行

grep -n -C1 'admin' /tmp/lesson/access.log

答案:输出 /admin 那行,以及它前一行和后一行,并在每行前加行号,方便定位问题。

例 10:递归搜索配置目录,只列出文件名

grep -rl 'listen' /etc/nginx/ 2>/dev/null

答案:列出 nginx 配置目录中所有内容包含 listen 的文件路径(如 /etc/nginx/nginx.conf)。-r 递归,-l 只给文件名,2>/dev/null 屏蔽权限不足的报错。

例 11:查找进程并排除 grep 自己

ps aux | grep '[s]shd'
# 或者
ps aux | grep sshd | grep -v grep

答案:只显示真正的 sshd 进程行。grep '[s]shd' 是常用技巧:grep 进程自己的命令行里是 [s]shd,不匹配这个模式,所以不会出现在结果里。

例 12:用返回值判断,实现「有就提示」

if grep -q '192.168.1.10' /tmp/lesson/access.log; then
    echo "该 IP 来过"
fi

答案:-q 不产生输出,只用退出码 0/1 做判断,脚本里最常用。

3.6 grep 常用套路

grep -vE '^[[:space:]]*(#|$)' /etc/ssh/sshd_config   # 过滤掉注释行和空行,只看有效配置
grep -E 'error|fail|critical' /var/log/messages      # 一次搜索多个关键字
grep -n 'pattern' -A3 -B3 file                       # 带上下文排查
grep -F -f blacklist.txt access.log                  # 按黑名单文件批量过滤(固定字符串,最快)
grep -c '' file                                      # 统计文件行数(等价 wc -l)
grep -r --include='*.conf' 'port' /etc/              # 只看指定后缀的文件

四、sed —— 改行(流编辑器)

4.1 定义

sed(Stream EDitor,流编辑器)按行读取文本,把当前行放进模式空间(pattern space),执行你给的编辑命令,然后默认把这一行输出,再读下一行。它不改内存里的原文件,除非你加 -i。

核心特征:非交互式、逐行处理、默认输出所有行。所以想「只输出某几行」,必须先用 -n 关掉默认输出。

4.2 语法结构

sed [选项] '地址定界 + 命令' 文件...
  • 地址定界:决定「对哪些行下手」,省略就是所有行。
  • 命令:决定「做什么」,如 p 打印、d 删除、s 替换、a 追加。
  • 多个命令可以用 -e、分号 ; 或脚本文件 -f 串联。
sed '3d' file                    # 命令:删除第 3 行
sed -n '/root/p' /etc/passwd     # 地址:含 root 的行;命令:打印
sed -n '3,5p' file               # 地址:3 到 5 行;命令:打印
sed -i 's/old/new/g' file        # 命令:全局替换,-i 直接写回文件

4.3 常用选项

选项 作用 说明
-n 取消默认输出 只输出 p 命令打印的内容,必备
-i 直接修改文件(in-place) -i.bak 会在改之前备份成 file.bak
-e 指定多条编辑脚本 sed -e 's/a/b/' -e 's/c/d/'
-f 从脚本文件读命令 命令多时用
-E / -r 使用扩展正则(ERE) 少写很多反斜杠
-s 多个文件分别按行计数(GNU) 默认多文件行号连续
--debug 显示模式空间执行过程(GNU) 调试神器
-u 无缓冲输出 配合管道实时看

⚠️ -i 是直接改文件、不可撤销的操作。第一次一定先用不带 -i 的命令看一眼效果,再加 -i.bak 备份。

4.4 地址定界(Address)

地址写法 含义 示例
省略 所有行 sed 's/a/b/'
n 第 n 行 sed -n '2p'
$ 最后一行 sed -n '$p'
n,m 第 n 到 m 行 sed -n '3,5p'
n,+m 从第 n 行往后 m 行 sed -n '3,+2p'
/正则/ 匹配正则的行 sed -n '/root/p'
\#正则# 自定义分隔符(正则含 / 时用) sed -n '\#/bin#p'
/r1/,/r2/ 从匹配 r1 的行到匹配 r2 的行 sed -n '/BEGIN/,/END/p'
first~step 从 first 行开始,每隔 step 行 sed -n '1~2p' 打印奇数行
0~n 从第一行开始每隔 n 行(GNU) sed '0~3d' 删 3、6、9… 行
! 取反,放在地址后面 sed '/^#/!d' 只保留非注释行

4.5 常用命令

命令 作用 示例
p 打印当前行 sed -n '2p'
d 删除当前行(不输出) sed '/^$/d' 删空行
s/// 替换 sed 's/a/b/'
y/// 字符一对一转换 sed 'y/abc/ABC/'
a\ 在当前行后追加 sed '2a hello'
i\ 在当前行前插入 sed '1i #!/bin/bash'
c\ 用新内容替换整行 sed '/^PORT=/c PORT=8080'
r 文件 在当前行后读入另一个文件 sed '1r header.txt'
w 文件 把当前行写入另一个文件 sed -n '1,10w head.txt'
q 处理到这里退出 sed '5q' 相当于 head -5
= 打印行号 sed '=' file
l 显示不可见字符($ 表示行尾) sed -n 'l'
n / N 读入下一行 / 追加下一行到模式空间 多行处理
h H g G x 保持空间(hold space)操作 倒序输出等高级用法

GNU sed 里 a、i、c 可以直接写在同一行:sed '2a hello';可移植写法是 sed '2a\hello'(多行用 \ 续行)。

4.6 s 替换命令详解(用得最多)

sed 's/原内容/新内容/标志' 文件

标志(flags):

标志 作用
无 只替换每行的第 1 处匹配
g 替换该行所有匹配(global)
p 如果替换成功,打印该行(配合 -n)
i 或 I 忽略大小写(GNU)
数字 只替换第 n 处匹配,如 s/a/b/3
w 文件 把替换成功的行写入文件

几个关键技巧:

sed 's#/usr/local#/opt#g' file           # 替换内容里有 /,换分隔符 #(也可用 | @ ,)
sed -E 's/([0-9]+) ([0-9]+)/\2 \1/' f    # 分组捕获,\1 \2 反向引用,这里交换两列
sed 's/root/[&]/' f                      # & 代表整个匹配到的内容 → [root]
sed -E 's/[0-9]+/X&X/' f                 # 数字两侧加 X
sed -E 's/^(.)(.*)$/\u\1\2/' f           # 首字母大写(GNU:\u 下一个字符、\U 全部大写、\L 全部小写)

4.7 短例题与答案

例 1:打印第 2 行(体会 -n 的作用)

sed '2p' students.txt      # 错误示范:每行都输出,第 2 行还多输出一次
sed -n '2p' students.txt   # 正确:只输出第 2 行

答案:1002 李四 78 92 70
原因:sed 默认输出所有行,p 又打印一次,所以第 2 行出现两遍;-n 关掉默认输出后才只剩 p 的结果。

例 2:打印第 3 到第 5 行

sed -n '3,5p' /tmp/lesson/students.txt

答案:

text 1003 王五 59 60 61 1004 赵六 100 95 98 1005 孙七 45 88 79

例 3:打印最后一行、打印奇数行

sed -n '$p' /tmp/lesson/students.txt     # 最后一行
sed -n '1~2p' /tmp/lesson/students.txt   # 第 1、3、5 行(奇数行)

答案:$p 输出 1005 孙七 45 88 79;1~2p 输出 1001、1003、1005 三行。

例 4:把 nginx 的监听端口 80 改成 8080(先看效果再改文件)

sed 's/listen 80;/listen 8080;/g' /etc/nginx/nginx.conf | diff /etc/nginx/nginx.conf -   # 预览差异
sed -i.bak 's/listen 80;/listen 8080;/g' /etc/nginx/nginx.conf                            # 备份后真正修改

答案:第一条只把结果打印出来和原文件对比,不动文件;第二条才写回。
⚠️ 不要图省事写 s/80/8080/g,那样 8000 会变成 808000。-i.bak 会生成 nginx.conf.bak,回滚用 mv nginx.conf.bak nginx.conf。

例 5:只替换每行的第一处 vs 全部替换

echo 'a a a' | sed 's/a/b/'     # b a a
echo 'a a a' | sed 's/a/b/g'    # b b b

答案:不加 g 只换第一个,加了 g 换全部。90% 的「为什么只改了一个」都是忘了 g。

例 6:删除空行和注释行(清理配置文件)

sed -E '/^[[:space:]]*(#|$)/d' /etc/ssh/sshd_config

答案:输出 sshd_config 中所有有效配置行,注释和空行都被删掉。
等价写法:grep -vE '^[[:space:]]*(#|$)' /etc/ssh/sshd_config。

例 7:删除第 1 行和最后 1 行

sed '1d;$d' /tmp/lesson/students.txt

答案:输出中间 3 行(1002~1004)。多个命令用 ; 分隔,$ 表示最后一行。

例 8:在匹配行的后面追加一行(改 sudoers 的经典操作)

sed -i.bak '/^root/a root ALL=(ALL) ALL' /etc/sudoers

答案:在 root 开头的那一行后面插入新行 root ALL=(ALL) ALL,并把原文件备份为 /etc/sudoers.bak。改完建议 visudo -c 检查语法。

例 9:把 SELINUX 那一整行换成新内容

sed -i 's/^SELINUX=.*/SELINUX=disabled/' /etc/selinux/config
# 或用 c 命令
sed -i '/^SELINUX=/c SELINUX=disabled' /etc/selinux/config

答案:两种写法都能把 SELINUX=enforcing 改成 SELINUX=disabled。c 命令是整行替换,s 只换匹配部分,这里用 .* 吃掉了整行剩余内容,效果一样。

例 10:只取两个标记之间的内容

sed -n '/BEGIN/,/END/p' file.conf

答案:输出从含有 BEGIN 的行开始、到含有 END 的行结束(含首尾)的整段内容。日志里截取某个请求、配置里取某个区块都用它。

例 11:交换两列(反向引用)

echo '192.168.1.10 80' | sed -E 's/^([0-9.]+) ([0-9]+)$/\2 \1/'

答案:80 192.168.1.10。([0-9.]+) 是第一组,([0-9]+) 是第二组,替换时写 \2 \1 就换了位置。

例 12:把行首的缩进(Tab/空格)去掉

sed 's/^[[:space:]]*//' file

答案:每行开头的空白被删除,内容左对齐。反过来 s/^/ / 是给每行加 4 个空格缩进。

例 13:给每行加行号

sed '=' /tmp/lesson/students.txt | sed 'N;s/\n/\t/'

答案:输出 1 1001 张三 90 85 88 这种「行号+Tab+内容」的格式。
= 单独打印行号会换行,N 把下一行拉进模式空间,再用 s/\n/\t/ 合并。更简单的办法是 cat -n 或 awk '{print NR"\t"$0}'。

例 14:倒序输出整个文件(保持空间高级用法)

sed -n '1!G;h;$p' /tmp/lesson/students.txt

答案:从最后一行到第一行输出,等价于 tac 命令。1!G 表示除第一行外都把保持空间追加到模式空间后面,h 存起来,$p 到最后一行才打印。

例 15:多条件组合(一条命令做多件事)

sed -n -e '/root/p' -e '/nologin/!p' /etc/passwd

答案:打印含 root 的行,以及不含 nologin 的行。

4.8 sed 修改文件的推荐流程

cp file file.bak                  # 1. 先备份
sed 's/old/new/g' file | diff file -   # 2. 预览差异
sed -i.bak 's/old/new/g' file     # 3. 确认无误再改

五、awk —— 拆列(字段处理与统计)

5.1 定义

awk 不只是命令,它是一门完整的文本处理编程语言(名字取自三位作者 Aho、Weinberger、Kernighan 的首字母)。

它的核心思想:把每一行按分隔符切成一个个「字段」,然后用 $1、$2、$3… 去引用它们,配合判断、循环、数组、函数,完成取列、统计、报表这类「需要动脑」的文本处理。

grep/sed 只能看到「一整行」,awk 能看到「一行里的第几列」——这是它最大的价值。

5.2 语法结构

awk [选项] 'BEGIN{开始前执行} 模式{动作} END{结束后执行}' 文件...
命令输出 | awk '模式{动作}'
  • BEGIN 块:处理第一行之前执行一次(打印表头、初始化变量)。
  • 模式:决定动作对哪些行执行;省略模式 = 所有行。
  • /正则/、关系表达式 $3>90、NR==1、范围 /start/,/end/
  • 动作:{...} 里的代码;省略动作 = 打印整行(即 {print $0})。
  • END 块:所有行处理完之后执行一次(汇总、求平均)。
awk -F: '{print $1}' /etc/passwd                     # 取第 1 列
awk -F: '$3>=1000 {print $1, $3}' /etc/passwd         # 条件 + 取列
awk 'BEGIN{FS=":"; print "用户"} {print $1} END{print "共", NR, "行"}' /etc/passwd

5.3 常用选项

选项 作用
-F 分隔符 指定字段分隔符,如 -F:、-F,、-F'\t'、-F'[ :]+'
-v 变量=值 在 awk 里定义变量(BEGIN 里就能用),如 -v n=90
-f 脚本 从文件读 awk 程序
--posix 严格 POSIX 模式

-F 等价于在 BEGIN 里写 FS=":";-v OFS='\t' 可以改输出分隔符。

5.4 内置变量(必须背)

变量 含义 示例
$0 当前整行 print $0 打印整行
$1…$n 第 n 个字段 $1 第一列
$NF 最后一列 print $NF
$(NF-1) 倒数第二列 print $(NF-1)
NF 当前行的字段个数 NF>5 字段超过 5 个
NR 已读入的总行号 NR==1 第一行
FNR 当前文件的行号(多文件时从头计) FNR==1 每个文件首行
FS 输入字段分隔符(默认空格/Tab) BEGIN{FS=","}
OFS 输出字段分隔符(默认空格) BEGIN{OFS="\t"}
RS 输入记录分隔符(默认换行) BEGIN{RS=""} 按段落
ORS 输出记录分隔符(默认换行)
FILENAME 当前文件名 print FILENAME
ARGV/ARGC 命令行参数数组 / 个数
ENVIRON 环境变量数组 ENVIRON["HOME"]

记忆:NF 是「这一行有几列」,NR 是「现在是第几行」,$NF 是「最后一列的内容」。

5.5 常用内置函数

函数 作用 示例
length(s) 字符串长度(无参数=整行长度) length($0)>80
substr(s,m,n) 从 m 开始取 n 个字符 substr($1,1,4)
index(s,t) t 在 s 中首次出现的位置 index($0,"error")
split(s,a,sep) 把 s 按 sep 切进数组 a,返回段数 split($1,a,".")
sub(r,s[,t]) 替换第一处(默认对 $0) sub(/x/,"y")
gsub(r,s[,t]) 替换全部 gsub(/ /,"_")
match(s,r) 匹配位置,设置 RSTART/RLENGTH match($0,/[0-9]+/)
sprintf(fmt,...) 格式化字符串(不输出) sprintf("%.2f", x)
toupper/tolower 转大写 / 小写 toupper($1)
int(x) 取整(截断) int(3.9) → 3
rand()/srand() 随机数 / 设置种子 srand(); rand()
system(cmd) 执行 shell 命令 system("date")

5.6 短例题与答案

例 1:取出 /etc/passwd 的用户名(第 1 列)

awk -F: '{print $1}' /etc/passwd

答案:输出 root、bin、daemon、sshd… 每行一个用户名。-F: 按冒号切,$1 就是第一列。

例 2:取用户名和 UID,中间用 Tab 分隔

awk -F: -v OFS='\t' '{print $1, $3}' /etc/passwd

答案:输出 root 0 这样的两列。print $1, $3 里的逗号会用 OFS 连接,改 OFS 就改了输出分隔符。

例 3:列出 UID≥1000 的普通用户

awk -F: '$3>=1000 && $3<65534 {print $1, $3}' /etc/passwd

答案:输出你自己创建的普通用户(如 zhangsan 1000)。&& 表示「并且」,|| 表示「或者」,! 表示「非」。

例 4:列出使用 /bin/bash 作为登录 shell 的用户

awk -F: '$7=="/bin/bash" {print $1}' /etc/passwd

答案:只输出 shell 字段完全等于 /bin/bash 的用户名。注意 awk 里比较字符串用 ==,= 是赋值。

例 5:统计 /etc/passwd 有多少个用户

awk -F: 'END{print NR}' /etc/passwd

答案:输出一个数字(行数即用户数)。END 在处理完最后一行后执行,此时 NR 就是总行数。

例 6:显示学生姓名和第 3 列(语文)成绩

awk '{print $2, $3}' /tmp/lesson/students.txt

答案:

text 张三 90 李四 78 王五 59 赵六 100 孙七 45

例 7:计算每个学生的总分和平均分(格式化输出)

awk '{printf "%-4s %-3s 总分=%-4d 平均=%.1f\n", $1, $2, $3+$4+$5, ($3+$4+$5)/3}' /tmp/lesson/students.txt

答案:

text 1001 张三 总分=263 平均=87.7 1002 李四 总分=240 平均=80.0 1003 王五 总分=180 平均=60.0 1004 赵六 总分=293 平均=97.7 1005 孙七 总分=212 平均=70.7

printf 的 %s 字符串、%d 整数、%.1f 保留 1 位小数、%-4s 左对齐占 4 位。

例 8:计算班级平均分

awk '{sum+=$3+$4+$5} END{printf "总分=%d 平均=%.1f\n", sum, sum/NR}' /tmp/lesson/students.txt

答案:总分=1188 平均=237.6。sum 没定义时默认是 0,可以直接累加。

例 9:统计语文 90 分以上的人数

awk '$3>=90 {n++} END{print "90分以上:", n, "人"}' /tmp/lesson/students.txt

答案:90分以上: 2 人(张三 90、赵六 100)。

例 10:查看第一行(相当于 head -1)、第 2~4 行

awk 'NR==1' /tmp/lesson/students.txt
awk 'NR>=2 && NR<=4' /tmp/lesson/students.txt

答案:第一条输出 1001 张三 90 85 88;第二条输出 1002、1003、1004 三行。只写模式不写动作时,默认动作是 print $0。

例 11:统计日志里每个 IP 的访问次数(高频面试题)

awk '{count[$1]++} END{for (ip in count) print count[ip], ip}' /tmp/lesson/access.log | sort -rn

答案:

text 3 192.168.1.10 2 192.168.1.11 2 192.168.1.13 1 192.168.1.12

count[$1]++ 是 awk 最经典的「关联数组计数」:以 IP 为下标,每出现一次加 1。

例 12:统计每种状态码出现的次数和总字节数

awk '{cnt[$9]++; size[$9]+=$10} END{for (s in cnt) printf "状态码=%s 次数=%d 字节=%d\n", s, cnt[s], size[s]}' /tmp/lesson/access.log

答案:输出的三行(顺序不固定):

text 状态码=200 次数=5 字节=5760 状态码=302 次数=1 字节=512 状态码=403 次数=1 字节=128 状态码=404 次数=1 字节=0

例 13:统计日志总流量

awk '{sum+=$10} END{print "总字节:", sum}' /tmp/lesson/access.log

答案:总字节: 6400,即约 6.25 KB。

例 14:取出访问路径(第 7 列),并统计 Top 3

awk '{print $7}' /tmp/lesson/access.log | sort | uniq -c | sort -rn | head -3

答案:

text 3 /index.html 2 /login 1 /b.css

例 15:给输出加表头(BEGIN 的典型用法)

awk 'BEGIN{print "姓名\t总分"} {print $2"\t"$3+$4+$5}' /tmp/lesson/students.txt

答案:第一行是 姓名 总分,下面是每个学生姓名和总分。

例 16:把 shell 变量传进 awk

n=90
awk -v n="$n" '$3>=n {print $2, $3}' /tmp/lesson/students.txt    # 正确
awk '$3>=$n {print $2}' /tmp/lesson/students.txt                  # 错误:单引号外面 $n 已被 shell 展开,awk 收到空值

答案:正确写法输出 张三 90、赵六 100。-v 是传变量的唯一标准姿势。

例 17:去重(保留第一次出现的行)

awk '!seen[$0]++' /tmp/lesson/students.txt

答案:重复行只保留第一行,且保持原顺序(sort -u 会改变顺序)。

例 18:把连续多个空格压缩成一个

awk '{$1=$1; print}' file

答案:$1=$1 会强制 awk 重新拼接 $0,此时字段之间统一用 OFS(默认一个空格)连接,多余空白就被吃掉了。等价写法 awk '{$1=$1}1'。

例 19:按「冒号或空格」等多个分隔符切分

echo 'root:0 0 /root' | awk -F'[: ]+' '{print $1, $2, $3}'

答案:root 0 0。-F 后面可以写正则,[: ]+ 表示「一个或多个冒号或空格」。

例 20:只输出含 ERROR 的行(awk 版 grep)

awk '/ERROR/ {print NR": "$0}' /tmp/lesson/access.log

答案:输出含 ERROR 的行,并在前面加 行号:。awk 模式里写 /正则/ 就是过滤。

5.7 awk 的 if / for / while 写法

# if-else:按分数评级
awk '{
    if ($3>=90)       g="A";
    else if ($3>=75)  g="B";
    else if ($3>=60)  g="C";
    else              g="D";
    print $2, $3, g
}' /tmp/lesson/students.txt

# for 循环:遍历数组
awk '{a[$1]++} END{for (k in a) print k, a[k]}' /tmp/lesson/access.log

# while / next / exit
awk '{ if ($3<60) {fail++; next}; pass++ } END{print pass, fail}' /tmp/lesson/students.txt
awk 'NR>3{exit} {print}' file        # 相当于 head -3

七、组合实战(管道)

三剑客真正的威力在于串起来用。

1. 只统计状态码 200 的总流量

grep ' 200 ' /tmp/lesson/access.log | awk '{sum+=$10} END{print sum" 字节"}'

2. 访问次数 Top 3 的 IP(grep + sort + uniq)

grep -oE '^([0-9]{1,3}\.){3}[0-9]{1,3}' /tmp/lesson/access.log \
  | sort | uniq -c | sort -rn | head -3

3. 看配置文件的有效内容(去掉注释、空行、行首空格)

sed -E '/^[[:space:]]*(#|$)/d; s/^[[:space:]]+//' /etc/ssh/sshd_config

4. 找出登录失败的 IP 并统计次数(日志排查套路)

grep 'Failed password' /var/log/secure \
  | awk '{print $(NF-3)}' \
  | sort | uniq -c | sort -rn | head -10

5. 批量重命名:把所有 .txt 里的 old 换成 new

for f in *.txt; do
  mv "$f" "$(echo "$f" | sed 's/old/new/')"
done

6. 生成一份「用户-UID-shell」报表

awk -F: 'BEGIN{printf "%-20s %-8s %s\n","用户","UID","SHELL"}
         {printf "%-20s %-8s %s\n", $1, $3, $7}' /etc/passwd | head -5

管道里每段各司其职:grep 负责「筛」、sed 负责「改」、awk 负责「算和排版」,再加上 sort、uniq、cut、wc、head 做辅助。


八、常见坑与注意事项

  1. 单引号 vs 双引号(最大的坑)
    awk '{print $1}' 正确;awk "{print $1}" 会被 shell 先把 $1 替换掉(通常是空),得到错误结果。三剑客的脚本一律用单引号。
  2. sed 替换忘了 g
    s/a/b/ 只换每行第一处,s/a/b/g 才换全部。
  3. sed -i 前不备份
    sed -i.bak 更安全;而且 -i 是「新建文件再改名」,会改变 inode、断开硬链接、不跟随符号链接,权限和属主也可能变。改系统配置文件前先 cp。
  4. grep 默认是 BRE
    +、?、|、()、{} 想直接用要加 -E,或在 BRE 里加反斜杠 \+、\?、\|。
  5. \d \w \s 不能用
    grep/sed 默认不支持;用 [0-9]、[[:alnum:]_]、[[:space:]],或 grep -P '\d'(PCRE 模式)。
  6. ^$ 只能匹配「空行」
    含空格/Tab 的「看起来空」的行匹配不到,要用 ^[[:space:]]*$。
  7. grep 遇到二进制文件只提示 "Binary file matches"
    加 -a(当文本处理)或 -I(直接跳过二进制)。
  8. 中文/UTF-8 下 . 可能按字节匹配
    确认 echo $LANG,必要时 export LANG=C.UTF-8;正则里少用「一个 . 代表一个汉字」的想法。
  9. awk 里 = 和 == 别混
    $3=100 是赋值(会改字段!),$3==100 才是比较。
  10. awk 里 print $1 $2 和 print $1, $2 不一样
    前者是直接拼接(会变成 1001张三),后者用 OFS 连接(默认空格)。
  11. 字符串按数字比较的隐患
    从日志取出的数字其实是字符串,一般 awk 能自动转换;不放心就写 $3+0>100 强制转数字。
  12. shell 变量传不进 awk
    用 -v name=value,不要指望单引号里的 $name 被展开。
  13. sort + uniq -c 的顺序
    uniq -c 只能统计相邻重复行,必须先 sort;按次数排序要 sort -rn。
  14. sed 的 a/i/c 在 GNU 与 BSD(macOS) 上语法不同
    Linux 上 sed '2a hello' 可以,macOS 需要 sed '2a\ + 换行 + hello'。写跨平台脚本时注意。
  15. 范围地址 /start/,/end/ 会「跨多次」重复匹配
    文件里有多个 start/end 时,会输出多段,不是只输出第一段。
  16. 别用 grep 筛 ps aux 时忘了排除自己
    用 grep '[s]shd' 或 | grep -v grep,否则结果里总有一条 grep 命令本身。

九、速查表

9.1 grep

grep 'pattern' file                 # 基本搜索
grep -in 'pattern' file             # 忽略大小写 + 行号
grep -v 'pattern' file              # 反向:不含 pattern 的行
grep -c 'pattern' file              # 统计匹配行数
grep -w 'word' file                 # 整词匹配
grep -o 'pattern' file              # 只输出匹配部分
grep -E 'a|b' file                  # 扩展正则(或)
grep -A3 -B3 'pattern' file         # 前后 3 行上下文
grep -rl 'pattern' /dir             # 递归,只列文件名
grep -vE '^[[:space:]]*(#|$)' file  # 去掉注释和空行
grep -q 'pattern' file; echo $?     # 只看是否有匹配(0 有 / 1 无)
grep -m1 'pattern' file             # 只取第一条匹配

9.2 sed

sed -n '5p' file                    # 打印第 5 行
sed -n '3,5p' file                  # 打印 3~5 行
sed -n '$p' file                    # 打印最后一行
sed -n '/re/p' file                 # 打印匹配行
sed '2d' file                       # 删除第 2 行
sed '/^$/d' file                    # 删除空行
sed '1d;$d' file                    # 删除首尾行
sed -n '1~2p' file                  # 打印奇数行
sed 's/old/new/' file               # 替换每行第一处
sed 's/old/new/g' file              # 替换全部
sed -E 's/(a)(b)/\2\1/' file        # 分组 + 反向引用
sed -i.bak 's/old/new/g' file       # 备份后原地修改
sed '2a newline' file               # 第 2 行后追加
sed '1i #!/bin/bash' file           # 第 1 行前插入
sed '/^PORT=/c PORT=8080' file      # 整行替换
sed -n '/BEGIN/,/END/p' file        # 取两个标记之间的内容
sed 's/^[ \t]*//' file              # 去行首空白
sed '$a END' file                   # 文件末尾追加

9.3 awk

awk '{print $1}' file                       # 取第 1 列
awk -F: '{print $1, $3}' /etc/passwd        # 指定分隔符取多列
awk -F: '$3>=1000{print $1}' /etc/passwd    # 条件过滤
awk 'NR==1' file                            # 只取第一行
awk 'NR>=2 && NR<=4' file                   # 取区间行
awk 'NR%2==1' file                          # 奇数行
awk 'END{print NR}' file                    # 行数(=wc -l)
awk '{sum+=$2} END{print sum}' file         # 求和
awk '{print $NF}' file                      # 最后一列
awk '{print $(NF-1)}' file                  # 倒数第二列
awk '{print NR": "$0}' file                 # 加行号
awk '!a[$0]++' file                         # 去重(保持顺序)
awk '{a[$1]++} END{for(i in a) print i,a[i]}' file   # 分组计数
awk '{$1=$1;print}' file                    # 压缩多余空白
awk -v OFS='\t' '{$1=$1;print}' file        # 转成 Tab 分隔
awk 'BEGIN{FS=":";OFS="\t"} {print $1,$3}' /etc/passwd
awk -F'[ :]+' '{print $1}' file             # 多分隔符
awk '/ERROR/{print}' file                   # 正则过滤
awk '{if($3>=60) print $2}' file            # if 判断
awk 'BEGIN{printf "%-10s %5s\n","name","score"} {printf "%-10s %5d\n",$1,$2}' file

9.4 辅助命令(常与三剑客搭配)

命令 作用
sort 排序,-n 数字序、-r 倒序、-k2 按第 2 列、-u 去重
uniq -c 统计相邻重复行(必须先 sort)
cut -d: -f1 按字符取列(简单场景可替代 awk)
wc -l 统计行数
tr 'a-z' 'A-Z' 字符转换
xargs 把结果传给下一个命令做参数

练习(38 题)

  1. 【grep】统计 /etc/passwd 中使用 bash 作为登录 shell 的用户有几个。
  2. 【grep】统计 /var/log/messages 里包含 error 的行数(不区分大小写)。
  3. 【grep】在 access.log 里找出状态码 404 的行,并显示行号。
  4. 【grep】递归搜索 /etc 下所有包含 PermitRootLogin 的文件名,屏蔽权限报错。
  5. 【grep】过滤掉配置文件里的注释行和空行,只留有效配置(以 sshd_config 为例)。
  6. 【grep】写一行命令:判断系统里是否存在 nobody 用户,有就输出「有」,没有输出「无」。
  7. 【grep】只提取出 access.log 中所有 IP,统计各 IP 出现次数并按次数降序。
  8. 【sed】只输出 students.txt 的第 3 到第 5 行。
  9. 【sed】只打印文件的第一行和最后一行。
  10. 【sed】删除 students.txt 的第 2 行和第 4 行,只显示结果,不修改原文件。
  11. 【sed】在 students.txt 的最后一行后面追加一行 -- 以上为全部成绩 --。
  12. 【sed】给文件每一行行首加 4 个空格(常用于代码块缩进)。
  13. 【sed】删除文件中的空行,以及以 # 开头的注释行。
  14. 【sed】去掉文件每行末尾的 \r(Windows 换行符)。
  15. 【sed】给 students.txt 的姓名后面加上 (同学)。
  16. 【sed】把 access.log 中所有状态码 404 改成 NOT_FOUND,并要求改之前自动备份。
  17. 【sed】在 /etc/sudoers 中含 root 的那一行后面追加新行 root ALL=(ALL) ALL。
  18. 【sed】把日期 2026-10-19 改写成 19/10/2026(用分组反向引用)。
  19. 【awk】输出 /etc/passwd 的用户名和 UID,中间用 Tab 分隔。
  20. 【awk】输出 /etc/passwd 的第 1 列和第 7 列,中间用 -> 连接。
  21. 【awk】输出 access.log 的 IP 列,格式为 行号:IP。
  22. 【综合】打印 /etc/passwd 的最后一列,并去重排序。
  23. 【awk】用 awk 计算 1 到 100 的累加和。
  24. 【awk】输出总分大于 250 的学生姓名。
  25. 【awk】列出 UID 大于等于 1000 的普通用户名。
  26. 【awk】只显示 access.log 中状态码不是 200 的行。
  27. 【awk】统计 access.log 的总字节数(最后一列之和)。
  28. 【awk】计算 students.txt 中每个学生的总分,并按「姓名 总分」输出。
  29. 【awk】找出数学(第 4 列)最高分是多少、是谁。
  30. 【awk】统计 /etc/passwd 里每种 shell 各有多少个用户。
  31. 【awk】统计 access.log 中每个 IP 的访问次数,按次数降序排列。
  32. 【awk】找出访问次数超过 2 次的 IP(只用 awk 完成)。
  33. 【awk】统计 access.log 中所有状态码 200 的请求一共传输了多少字节。
  34. 【综合】找出被访问最多的 3 个路径(第 7 列)。
  35. 【综合】找出 access.log 中状态码为 200 的请求,按字节数降序取前 3 条。
  36. 【综合】输出 /etc/passwd 中 UID 最小的 5 个用户的「用户名 UID shell」。
  37. 【综合】在 /etc/passwd 中找出 UID 为 0 的用户(用 grep 和 awk 各写一遍)。
  38. 【综合】同一个需求「取文件第 2 列」,分别用 awk 和 sed 实现,比较优劣。

答案与解析

1.

grep -c 'bash$' /etc/passwd

-c 只输出行数;$ 锁定行尾,避免把 /bin/bashxxx 之类的误算进来。

2.

grep -ic 'error' /var/log/messages

-i 忽略大小写,-c 输出行数。注意 -c 统计的是行数,同一行出现多次只算 1。

3.

grep -n ' 404 ' access.log

两侧留空格是为了精确命中状态码字段;不加空格可能匹配到字节数里的 404。-n 显示行号。

4.

grep -rl 'PermitRootLogin' /etc 2>/dev/null

结果一般包括 /etc/ssh/sshd_config 和 /etc/ssh/sshd_config.d/*.conf。

5.

grep -vE '^[[:space:]]*(#|$)' /etc/ssh/sshd_config

-v 反向选择,-E 用扩展正则。^[[:space:]]*(#|$) 表示「行首可选空白,然后是 # 或行尾」。

6.

grep -q '^nobody:' /etc/passwd && echo 有 || echo 无

-q 静默,只看退出码:0=匹配到,1=没匹配到,2=出错。&& 与 || 根据返回值决定走哪边。

7.

grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' access.log \
  | sort | uniq -c | sort -rn

-o 只输出匹配部分,-E 让 {} 不用转义。结果:3 192.168.1.10 排第一。uniq -c 只能统计相邻重复行,所以必须先 sort。

8.

sed -n '3,5p' students.txt

-n 关掉默认输出,p 才打印。不加 -n 的话每行都会输出,第 3~5 行还会重复一遍。

9.

sed -n '1p;$p' file

1p 打印第一行,$ 表示最后一行,多个地址用分号连接。-n 不能省。

10.

sed -e '2d' -e '4d' /tmp/lesson/students.txt
# 或
sed '2d;4d' /tmp/lesson/students.txt

地址按输入行号判断,所以删除第 2 行不会影响「第 4 行」的定位,结果剩 3 行(1001、1003、1005)。

11.

sed '$a -- 以上为全部成绩 --' /tmp/lesson/students.txt

$ 是最后一行,a 是其后追加(GNU 语法)。写回文件要加 -i。

12.

sed 's/^/    /' file

^ 匹配行首,把它替换成 4 个空格。反过来去掉行首空白是 s/^[[:space:]]*//。

13.

sed -E '/^[[:space:]]*(#|$)/d' file.conf

d 删除匹配行。只想删「纯空行」用 sed '/^$/d';含空格的空行匹配不到,所以这里用 [[:space:]]*。

14.

sed -i 's/\r$//' file
# 或者用工具
dos2unix file

\r 是回车符,在 sed -n 'l' 里能看到它显示为 \r$。

15.

sed -E 's/^([0-9]+) ([^ ]+)/\1 \2(同学)/' /tmp/lesson/students.txt

结果:1001 张三(同学) 90 85 88 这样。\1 \2 是保留原学号和姓名。

16.

sed -i.bak 's/ 404 / NOT_FOUND /g' /tmp/lesson/access.log

-i.bak 先备份成 access.log.bak 再改;两侧空格防止误伤字节数里的 404。

17.

sed -i.bak '/^root/a root ALL=(ALL) ALL' /etc/sudoers

a = append,在匹配行之后追加;i 是在之前插入。改完用 visudo -c 检查语法。

18.

echo '2026-10-19' | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/'

三组分别捕获年、月、日,用 \3 \2 \1 调换顺序;替换部分里的斜杠要写成 \/,或者直接换分隔符:s#…#…#。

19.

awk -F: -v OFS='\t' '{print $1, $3}' /etc/passwd

-F: 指定输入分隔符,-v OFS='\t' 改输出分隔符。注意是 print $1, $3(逗号),不是 $1 $3。

20.

awk -F: '{print $1 " -> " $7}' /etc/passwd

结果形如 root -> /bin/bash。字符串拼接直接用空格或 "" 相邻书写即可。

21.

awk '{print NR ": " $1}' /tmp/lesson/access.log

结果:1: 192.168.1.10 起,共 8 行。

22.

awk -F: '{print $NF}' /etc/passwd | sort -u

结果:/bin/bash、/bin/sync、/sbin/halt、/sbin/nologin、/sbin/shutdown 等。

23.

awk 'BEGIN{s=0; for(i=1;i<=100;i++) s+=i; print s}'

结果:5050。不需要输入文件,纯计算写在 BEGIN 里。

24.

awk '$3+$4+$5 > 250 {print $2}' /tmp/lesson/students.txt

结果:张三、赵六(总分 263、293)。

25.

awk -F: '$3>=1000 && $3<65534 {print $1, $3}' /etc/passwd

awk 的「模式」写在动作前面,条件成立才执行 {print}。&& 并且、|| 或者。

26.

awk '$9 != 200' /tmp/lesson/access.log
# 或
grep -v ' 200 ' /tmp/lesson/access.log

结果:302、404、403 那三行。awk 版更精确(按字段比较),grep 版靠空格兜住。

27.

awk '{sum+=$NF} END{print sum}' access.log

$NF 是最后一列。END 在处理完所有行后执行。本例结果 6400 字节。

28.

awk '{print $2, $3+$4+$5}' students.txt
# 格式化版
awk '{printf "%-4s %s 总分=%d\n", $1, $2, $3+$4+$5}' students.txt

字段默认按空白切分;三科成绩相加即总分。结果:张三 263、李四 240、王五 180、赵六 293、孙七 212。

29.

awk 'NR==1{max=$4; name=$2}
     $4>max{max=$4; name=$2}
     END{print name, max}' students.txt

第一行初始化最大值,之后逐行比较。结果:赵六 95。用 END 保证所有行都处理完才输出。

30.

awk -F: '{a[$NF]++} END{for (s in a) printf "%-20s %d\n", s, a[s]}' /etc/passwd

$NF = 最后一列(shell)。a[$NF]++ 分类计数,END 里格式化输出。常见结果:/sbin/nologin 最多。

31.

awk '{a[$1]++} END{for (i in a) print a[i], i}' /tmp/lesson/access.log | sort -rn

结果:3 192.168.1.10、2 192.168.1.11、2 192.168.1.13、1 192.168.1.12。

32.

awk '{a[$1]++} END{for (i in a) if (a[i]>2) printf "%s 访问了 %d 次\n", i, a[i]}' /tmp/lesson/access.log

结果:192.168.1.10 访问了 3 次。

33.

awk '$9==200 {sum+=$10} END{print sum}' access.log

也可以 grep ' 200 ' access.log | awk '{s+=$10} END{print s}'。结果 5760 字节;用 awk 单条更省一次管道。

34.

awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -3

结果:/index.html 3 次、/login 2 次、其余各 1 次。sort → uniq -c → sort -rn 是统计频次的固定三连。

35.

awk '$9==200 {print $10, $7}' /tmp/lesson/access.log | sort -rn | head -3

结果:

text 2048 /b.css 1024 /index.html 1024 /index.html

36.

sort -t: -k3 -n /etc/passwd | head -5 | awk -F: '{print $1, $3, $7}'

sort -t: -k3 -n 以冒号分隔、按第 3 列数字排序,再取前 5 行交给 awk 排版。三剑客 + sort/cut 组合是日常最顺手的写法。

37.

grep ':0:0:' /etc/passwd                  # 结果:root:x:0:0:root:/root:/bin/bash
awk -F: '$3==0 {print $1}' /etc/passwd   # 结果:root

grep 靠「冒号 0 冒号 0 冒号」来锁定 UID/GID,脆弱;awk 直接判断字段,更可靠。

38.

awk '{print $2}' file
sed -E 's/^[[:space:]]*[^[:space:]]+[[:space:]]+//' file   # 只适合「取第 2 列之后」,且空格数不固定时不可靠

awk 天生按字段处理,取任意列都简单;sed 没有字段概念,只能用正则「删掉第 1 列」,列数一多就难写。取列请用 awk。


附:学习小结

  • grep = 行的搜索器:grep [选项] '模式' 文件,重点记 -i -v -n -c -o -E -r -A/-B/-C。
  • sed = 行的编辑器:sed [选项] '地址+命令' 文件,重点记 -n -i -E、s///g、d、p、a/i/c。
  • awk = 行的计算器:awk [选项] 'BEGIN{} 模式{动作} END{}' 文件,重点记 -F、$1/$NF、NR/NF/FS/OFS、数组计数、printf。
  • 三者都逐行流式处理,都能接管道;正则基础通用,区别在 BRE/ERE 的转义。
  • 真正的熟练来自「先想清楚是找行、改行还是算列」,再决定用谁。

练习题按 grep → sed → awk → 综合 分组,组内由易到难;其中有 24 道原先是独立的练习册页面(01_文本三剑客练习.html,已归档),现已并入本文件,生成 html 时与其他笔记一样自动变成折叠答案的自测区。

我的笔记 · 生成于 2026-10-26 00:46