文本三剑客:grep / sed / awk
一句话记忆: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做辅助。
八、常见坑与注意事项
- 单引号 vs 双引号(最大的坑)
awk '{print $1}'正确;awk "{print $1}"会被 shell 先把$1替换掉(通常是空),得到错误结果。三剑客的脚本一律用单引号。 - sed 替换忘了
g
s/a/b/只换每行第一处,s/a/b/g才换全部。 sed -i前不备份
sed -i.bak更安全;而且-i是「新建文件再改名」,会改变 inode、断开硬链接、不跟随符号链接,权限和属主也可能变。改系统配置文件前先cp。- grep 默认是 BRE
+、?、|、()、{}想直接用要加-E,或在 BRE 里加反斜杠\+、\?、\|。 \d\w\s不能用
grep/sed 默认不支持;用[0-9]、[[:alnum:]_]、[[:space:]],或grep -P '\d'(PCRE 模式)。^$只能匹配「空行」
含空格/Tab 的「看起来空」的行匹配不到,要用^[[:space:]]*$。- grep 遇到二进制文件只提示 "Binary file matches"
加-a(当文本处理)或-I(直接跳过二进制)。 - 中文/UTF-8 下
.可能按字节匹配
确认echo $LANG,必要时export LANG=C.UTF-8;正则里少用「一个.代表一个汉字」的想法。 - awk 里
=和==别混
$3=100是赋值(会改字段!),$3==100才是比较。 - awk 里
print $1 $2和print $1, $2不一样
前者是直接拼接(会变成1001张三),后者用OFS连接(默认空格)。 - 字符串按数字比较的隐患
从日志取出的数字其实是字符串,一般 awk 能自动转换;不放心就写$3+0>100强制转数字。 - shell 变量传不进 awk
用-v name=value,不要指望单引号里的$name被展开。 - sort + uniq -c 的顺序
uniq -c只能统计相邻重复行,必须先sort;按次数排序要sort -rn。 - sed 的
a/i/c在 GNU 与 BSD(macOS) 上语法不同
Linux 上sed '2a hello'可以,macOS 需要sed '2a\+ 换行 +hello'。写跨平台脚本时注意。 - 范围地址
/start/,/end/会「跨多次」重复匹配
文件里有多个 start/end 时,会输出多段,不是只输出第一段。 - 别用
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 |
把结果传给下一个命令做参数 |
附:学习小结
- 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 时与其他笔记一样自动变成折叠答案的自测区。
练习(38 题)
【grep】统计 /etc/passwd 中使用 bash 作为登录 shell 的用户有几个。
查看答案
grep -c 'bash$' /etc/passwd
-c 只输出行数;$ 锁定行尾,避免把
/bin/bashxxx之类的误算进来。
【grep】统计 /var/log/messages 里包含 error 的行数(不区分大小写)。
查看答案
grep -ic 'error' /var/log/messages
-i忽略大小写,-c输出行数。注意-c统计的是行数,同一行出现多次只算 1。
【grep】在 access.log 里找出状态码 404 的行,并显示行号。
查看答案
grep -n ' 404 ' access.log
两侧留空格是为了精确命中状态码字段;不加空格可能匹配到字节数里的 404。-n 显示行号。
【grep】递归搜索 /etc 下所有包含 PermitRootLogin 的文件名,屏蔽权限报错。
查看答案
grep -rl 'PermitRootLogin' /etc 2>/dev/null
结果一般包括
/etc/ssh/sshd_config和/etc/ssh/sshd_config.d/*.conf。
【grep】过滤掉配置文件里的注释行和空行,只留有效配置(以 sshd_config 为例)。
查看答案
grep -vE '^[[:space:]]*(#|$)' /etc/ssh/sshd_config
-v 反向选择,-E 用扩展正则。
^[[:space:]]*(#|$)表示「行首可选空白,然后是 # 或行尾」。
【grep】写一行命令:判断系统里是否存在 nobody 用户,有就输出「有」,没有输出「无」。
查看答案
grep -q '^nobody:' /etc/passwd && echo 有 || echo 无
-q 静默,只看退出码:0=匹配到,1=没匹配到,2=出错。
&&与||根据返回值决定走哪边。
【grep】只提取出 access.log 中所有 IP,统计各 IP 出现次数并按次数降序。
查看答案
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。
【sed】只输出 students.txt 的第 3 到第 5 行。
查看答案
sed -n '3,5p' students.txt
-n 关掉默认输出,
p才打印。不加-n的话每行都会输出,第 3~5 行还会重复一遍。
【sed】只打印文件的第一行和最后一行。
查看答案
sed -n '1p;$p' file
1p打印第一行,$表示最后一行,多个地址用分号连接。-n 不能省。
【sed】删除 students.txt 的第 2 行和第 4 行,只显示结果,不修改原文件。
查看答案
sed -e '2d' -e '4d' /tmp/lesson/students.txt
# 或
sed '2d;4d' /tmp/lesson/students.txt
地址按输入行号判断,所以删除第 2 行不会影响「第 4 行」的定位,结果剩 3 行(1001、1003、1005)。
【sed】在 students.txt 的最后一行后面追加一行 -- 以上为全部成绩 --。
查看答案
sed '$a -- 以上为全部成绩 --' /tmp/lesson/students.txt
$是最后一行,a是其后追加(GNU 语法)。写回文件要加-i。
【sed】给文件每一行行首加 4 个空格(常用于代码块缩进)。
查看答案
sed 's/^/ /' file
^匹配行首,把它替换成 4 个空格。反过来去掉行首空白是s/^[[:space:]]*//。
【sed】删除文件中的空行,以及以 # 开头的注释行。
查看答案
sed -E '/^[[:space:]]*(#|$)/d' file.conf
d 删除匹配行。只想删「纯空行」用
sed '/^$/d';含空格的空行匹配不到,所以这里用[[:space:]]*。
【sed】去掉文件每行末尾的 \r(Windows 换行符)。
查看答案
sed -i 's/\r$//' file
# 或者用工具
dos2unix file
\r是回车符,在sed -n 'l'里能看到它显示为\r$。
【sed】给 students.txt 的姓名后面加上 (同学)。
查看答案
sed -E 's/^([0-9]+) ([^ ]+)/\1 \2(同学)/' /tmp/lesson/students.txt
结果:
1001 张三(同学) 90 85 88这样。\1\2是保留原学号和姓名。
【sed】把 access.log 中所有状态码 404 改成 NOT_FOUND,并要求改之前自动备份。
查看答案
sed -i.bak 's/ 404 / NOT_FOUND /g' /tmp/lesson/access.log
-i.bak先备份成access.log.bak再改;两侧空格防止误伤字节数里的 404。
【sed】在 /etc/sudoers 中含 root 的那一行后面追加新行 root ALL=(ALL) ALL。
查看答案
sed -i.bak '/^root/a root ALL=(ALL) ALL' /etc/sudoers
a = append,在匹配行之后追加;i 是在之前插入。改完用
visudo -c检查语法。
【sed】把日期 2026-10-19 改写成 19/10/2026(用分组反向引用)。
查看答案
echo '2026-10-19' | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/'
三组分别捕获年、月、日,用
\3 \2 \1调换顺序;替换部分里的斜杠要写成\/,或者直接换分隔符:s#…#…#。
【awk】输出 /etc/passwd 的用户名和 UID,中间用 Tab 分隔。
查看答案
awk -F: -v OFS='\t' '{print $1, $3}' /etc/passwd
-F: 指定输入分隔符,-v OFS='\t' 改输出分隔符。注意是
print $1, $3(逗号),不是$1 $3。
【awk】输出 /etc/passwd 的第 1 列和第 7 列,中间用 -> 连接。
查看答案
awk -F: '{print $1 " -> " $7}' /etc/passwd
结果形如
root -> /bin/bash。字符串拼接直接用空格或""相邻书写即可。
【awk】输出 access.log 的 IP 列,格式为 行号:IP。
查看答案
awk '{print NR ": " $1}' /tmp/lesson/access.log
结果:
1: 192.168.1.10起,共 8 行。
【综合】打印 /etc/passwd 的最后一列,并去重排序。
查看答案
awk -F: '{print $NF}' /etc/passwd | sort -u
结果:
/bin/bash、/bin/sync、/sbin/halt、/sbin/nologin、/sbin/shutdown等。
【awk】用 awk 计算 1 到 100 的累加和。
查看答案
awk 'BEGIN{s=0; for(i=1;i<=100;i++) s+=i; print s}'
结果:
5050。不需要输入文件,纯计算写在 BEGIN 里。
【awk】输出总分大于 250 的学生姓名。
查看答案
awk '$3+$4+$5 > 250 {print $2}' /tmp/lesson/students.txt
结果:
张三、赵六(总分 263、293)。
【awk】列出 UID 大于等于 1000 的普通用户名。
查看答案
awk -F: '$3>=1000 && $3<65534 {print $1, $3}' /etc/passwd
awk 的「模式」写在动作前面,条件成立才执行
{print}。&&并且、||或者。
【awk】只显示 access.log 中状态码不是 200 的行。
查看答案
awk '$9 != 200' /tmp/lesson/access.log
# 或
grep -v ' 200 ' /tmp/lesson/access.log
结果:302、404、403 那三行。awk 版更精确(按字段比较),grep 版靠空格兜住。
【awk】统计 access.log 的总字节数(最后一列之和)。
查看答案
awk '{sum+=$NF} END{print sum}' access.log
$NF 是最后一列。END 在处理完所有行后执行。本例结果 6400 字节。
【awk】计算 students.txt 中每个学生的总分,并按「姓名 总分」输出。
查看答案
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。
【awk】找出数学(第 4 列)最高分是多少、是谁。
查看答案
awk 'NR==1{max=$4; name=$2}
$4>max{max=$4; name=$2}
END{print name, max}' students.txt
第一行初始化最大值,之后逐行比较。结果:
赵六 95。用 END 保证所有行都处理完才输出。
【awk】统计 /etc/passwd 里每种 shell 各有多少个用户。
查看答案
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最多。
【awk】统计 access.log 中每个 IP 的访问次数,按次数降序排列。
查看答案
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。
【awk】找出访问次数超过 2 次的 IP(只用 awk 完成)。
查看答案
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 次。
【awk】统计 access.log 中所有状态码 200 的请求一共传输了多少字节。
查看答案
awk '$9==200 {sum+=$10} END{print sum}' access.log
也可以
grep ' 200 ' access.log | awk '{s+=$10} END{print s}'。结果 5760 字节;用 awk 单条更省一次管道。
【综合】找出被访问最多的 3 个路径(第 7 列)。
查看答案
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -3
结果:
/index.html3 次、/login2 次、其余各 1 次。sort → uniq -c → sort -rn 是统计频次的固定三连。
【综合】找出 access.log 中状态码为 200 的请求,按字节数降序取前 3 条。
查看答案
awk '$9==200 {print $10, $7}' /tmp/lesson/access.log | sort -rn | head -3
结果:
text 2048 /b.css 1024 /index.html 1024 /index.html
【综合】输出 /etc/passwd 中 UID 最小的 5 个用户的「用户名 UID shell」。
查看答案
sort -t: -k3 -n /etc/passwd | head -5 | awk -F: '{print $1, $3, $7}'
sort -t: -k3 -n 以冒号分隔、按第 3 列数字排序,再取前 5 行交给 awk 排版。三剑客 + sort/cut 组合是日常最顺手的写法。
【综合】在 /etc/passwd 中找出 UID 为 0 的用户(用 grep 和 awk 各写一遍)。
查看答案
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 直接判断字段,更可靠。
【综合】同一个需求「取文件第 2 列」,分别用 awk 和 sed 实现,比较优劣。
查看答案
awk '{print $2}' file
sed -E 's/^[[:space:]]*[^[:space:]]+[[:space:]]+//' file # 只适合「取第 2 列之后」,且空格数不固定时不可靠
awk 天生按字段处理,取任意列都简单;sed 没有字段概念,只能用正则「删掉第 1 列」,列数一多就难写。取列请用 awk。