Appearance
正则表达式
分类
工具 | 默认支持的正则类型 | 扩展正则启用方法 |
---|---|---|
grep | 基本正则(BRE) | grep -E 或 egrep |
sed | 基本正则(BRE) | sed -E (GNU)或 sed -r |
awk | 扩展正则(ERE) | 默认支持,无需额外选项 |
基本元字符
元字符 | 作用 | 示例 |
---|---|---|
^ | 匹配行首 | ^admin |
$ | 匹配行尾 | error$ |
^$ | 匹配空行 | ^$ |
. | 匹配任意单个字符 | a.c → abc, a1c |
* | 匹配前一个字符0次到无限次 | go*d → gd, good |
\ | 转义符 | \. ,\* ,\\ |
+ | 匹配前一个字符1次到无限次 | go+d → god, gooood |
? | 匹配前一个字符 0或1 次 | colou?r → color/colour |
[] | 匹配中括号中的任意一个字符 | [aeiou] → 匹配元音字母 |
[^] | 取反,不匹配中括号内的字符 | [^0-9] → 非数字字符 |
^[] | 以中括号内任意字符开头 | ^[abc] |
` | ` | 逻辑或(扩展正则) |
.* | 表示任意 | .* |
.*? | 转为懒惰(尽可能少),匹配到第一个满足条件 | .*? |
{n} | 精确匹配 n 次(扩展正则) | a{3} → aaa |
{n,} | 至少匹配 n 次(扩展正则) | a{2,} → aa, aaa… |
{,n} | 匹配前一个字符0次到n次(扩展正则) | |
{n,m} | 匹配前一个字符n次到m次(扩展正则) | a{2,4} → aa, aaa, aaaa |
() | 用于分组,可以将多个字符作为一个整体进行匹配 | (abc) |
转义字符
字符 | 功能描述 | 示例数据 |
---|---|---|
\. | 匹配字符 . | \. |
\* | 匹配字符 * | \* |
\+ | 匹配字符 + | \+ |
\? | 匹配字符 ? | \? |
$ | 匹配字符 ( | $ |
$ | 匹配字符 ) | $ |
$$ | 匹配字符 [ | $$ |
$$ | 匹配字符 ] | $$ |
\{ | 匹配字符 { | \{ |
\} | 匹配字符 } | \} |
\^ | 匹配字符 ^ | \^ |
\$ | 匹配字符 $ | \$ |
` | ` | 匹配字符 ` |
预定义字符类
字符 | 功能描述 | 适用场景 |
---|---|---|
\d | 匹配任意一个数字字符 | 匹配任意一个数字,如 0 、1 、9 等。 |
\D | 匹配任意一个非数字字符 | 匹配任意一个非数字字符,如 a 、b 、_ 等。 |
\w | 匹配任意一个字母、数字或下划线字符 | 匹配任意一个字母、数字或下划线,如 a 、1 、_ 等。 |
\W | 匹配任意一个非字母、数字或下划线字符 | 匹配任意一个非字母、数字或下划线字符,如 ! 、@ 、# 等。 |
\s | 匹配任意一个空白字符 | 匹配任意一个空白字符,如空格、制表符、换行符等。 |
\S | 匹配任意一个非空白字符 | 匹配任意一个非空白字符,如 a 、1 、_ 等。 |
不同工具支持对比
工具 | 支持预定义字符 | 启用方式 |
---|---|---|
grep | 仅-P 模式 | grep -P '\d' |
sed | 不支持 | 需用[0-9] 替代 |
awk | 部分支持 | \s \S 可用 |
perl | 完全支持 | 原生支持 |
贪婪与懒惰匹配
字符 | 功能描述 | 示例数据 | 适用场景 |
---|---|---|---|
*? | 懒惰匹配前面的字符零次或多次 | a*? | 懒惰匹配前面的字符零次或多次,如 a*? 匹配 a 时只匹配一个 a 。 |
+? | 懒惰匹配前面的字符一次或多次 | a+? | 懒惰匹配前面的字符一次或多次,如 a+? 匹配 aa 时只匹配一个 a 。 |
?? | 懒惰匹配前面的字符零次或一次 | a?? | 懒惰匹配前面的字符零次或一次,如 a?? 匹配 aa 时只匹配一个 a 。 |
{n,}? | 懒惰匹配前面的字符至少 n 次 | a{2,}? | 懒惰匹配前面的字符至少 n 次,如 a{2,}? 匹配 aaa 时只匹配 aa 。 |
{n,m}? | 懒惰匹配前面的字符至少 n 次,至多 m 次 | a{2,4}? | 懒惰匹配前面的字符至少 n 次,至多 m 次,如 a{2,4}? 匹配 aaaa 时只匹配 aa 。 |
工具差异与注意事项
工具/语法 | 转义规则特点 | 示例对比 |
---|---|---|
BRE(基础正则) | 需转义 + 、? 、` | 、 {}` 等扩展元字符 |
ERE(扩展正则) | 直接支持 + 、? 、` | `,无需转义 |
PCRE(Perl兼容) | 支持零宽断言、命名分组等高级功能 | grep -P '\d+(?=元)' → 匹配数字后接 "元" |
Shell 引号处理 | 单引号 '' 避免变量扩展,双引号 "" 需二次转义 | grep "\\bword\\b" → 双引号需转义 \ |
grep - 文本搜索
快速过滤文本行,支持正则表达式匹配。
语法格式
bash
grep [选项] 模式 [文件/目录...]
选项
选项 | 作用 | 示例 |
---|---|---|
-i | 忽略大小写 | grep -i "error" log.txt → 搜索包含 error/Error/ERROR 的行 |
-v | 反向匹配(排除匹配行) | grep -v "success" data.csv → 显示不包含 "success" 的行 |
-n | 显示匹配行的行号 | grep -n "timeout" app.log → 输出格式:行号:匹配内容 |
-r | 递归搜索目录下的所有文件 | grep -r "192.168.1.1" /etc/ → 在 /etc/ 目录所有文件中搜索 IP |
-h | 搜索多文件时不显示文件名 | grep -h "timeout" app.log |
-H | 搜索多文件时显示文件名 | grep -H "timeout" app.log |
-c | 统计匹配行数(而非显示内容) | grep -c "404" access.log → 返回 404 错误的出现次数 |
-l | 仅显示包含匹配项的文件名 | grep -l "panic" *.log → 列出所有包含 "panic" 的日志文件名 |
-w | 全词匹配(避免部分匹配) | grep -w "port" config.conf → 只匹配 "port",排除 "export" 等部分匹配 |
-A NUM | 显示匹配行及后续 NUM 行 | grep -A 3 "Exception" app.log → 显示异常行及其后 3 行(上下文分析) |
-B NUM | 显示匹配行及前 NUM 行 | grep -B 2 "Segmentation fault" crash.log → 查看崩溃前的 2 行日志 |
-C NUM | 显示匹配行前后各 NUM 行 | grep -C 2 "deadlock" system.log → 显示死锁行附近 2 行上下文 |
-E | 启用扩展正则(ERE) | grep -E "error" |
-o | 仅输出匹配部分 | grep -Eo "[0-9]+\.[0-9]+\.[0-9]+" data |
经典案例
bash
# 搜索包含 "error" 或 "404" 的行,并显示前后2行
grep -C 2 -E "error|404" access.log
# 统计所有 .log 文件中 "timeout" 出现的次数
grep -rch "timeout" /var/log/*.log
# 提取所有 IPv4 地址
grep -Eo "([0-9]{1,3}\.){3}[0-9]{1,3}" data.txt
# 统计 Nginx 日志中 5xx 错误的数量
grep -E " 5[0-9]{2} " access.log | awk '{print $9}' | sort | uniq -c
# 查找包含 "error" 但不包含 "warning" 的行
grep "error" file.log | grep -v "warning"
sed - 流式文本编辑
对文本进行流式编辑(替换、删除、插入等),支持正则表达式。sed 工作原理是从文件中逐行读取内容。
语法格式
bash
sed [选项] '地址范围/操作/参数' 文件
sed [选项] '脚本命令' 输入文件
sed [选项] -f 脚本文件 输入文件
选项
选项 | 描述 | 示例 |
---|---|---|
-e script | 执行多条指令 | sed -e 's/foo/bar/' -e 's/abc/123/' file |
-f file | 从文件读取脚本命令 | sed -f script.sed file |
-i[后缀] | 直接修改文件(可选备份后缀) | sed -i.bak 's/foo/bar/' file |
-n | 禁止默认输出 | sed -n '1,5p' file |
-r | 使用扩展正则表达式 | sed -r 's/[0-9]+/NUM/g' file.txt |
-l | 指定行长度(与 l 命令配合显示行内容) | sed -n 'l 50' file.txt (每行最多显示 50 个字符) |
-s | 将多个输入文件视为独立文本流处理 | sed -s 's/foo/bar/' file1.txt file2.txt |
操作
命令 | 解释说明 | 示例 |
---|---|---|
a | 追加,在指定行后添加一行或多行文本* | sed '2a\This is appended text' file.txt |
c | 替换指定行的内容 | sed '3c\This replaces line 3' file.txt |
d | 删除指定的行* | sed '/pattern/d' file.txt |
i | 插入,在指定行前添加一行或多行文本* | sed '1i\This is inserted text' file.txt |
H | 将指定行内容复制 | sed '1{H;d}; 2G' file.txt |
g | 把保持空间的内容复制到模式空间 | sed '1h; 2g' file.txt |
G | 将复制的内容粘贴到指定行 | sed '1h; 2G' file.txt |
l | 打印不可见的字符 | sed -n 'l' file.txt |
n | 清空模式空间的内容并读入下一行 | sed 'n;d' file.txt |
p | 打印内容,通常与-n 选项一起使用* | sed -n '/pattern/p' file.txt |
P | 打印模式空间的内容,直到遇到换行符\n 结束操作 | sed -n 'N;P' file.txt |
r | 从指定文件读取数据 | sed '/pattern/r otherfile.txt' file.txt |
s | 替换文本,s#old#new#g (g 是替换标志,不是命令)* | sed 's/foo/bar/g' file.txt |
w | 另存,把模式空间的内容保存到文件中 | sed -n '/pattern/w output.txt' file.txt |
y | 根据对应位置转换字符 | sed 'y/abc/ABC/' file.txt |
:label | 定义一个标签 | sed ':start; s/A/B/; t start' file.txt |
b label | 执行该标签后面的命令 | sed '/pattern/b end; s/A/B/; :end' file.txt |
t | 如果前面的命令执行成功,跳转到指定标签 | sed 's/A/B/; t done; s/C/D/; :done' file.txt |
注意
在匹配字符场景下操作
a
、i
、c
需要后面加\
bashsed '/user/a\这是匹配字符下一行追加' filename sed '/user/i\这是匹配字符上一行追加' filename sed '/user/c\这是匹配字符行替换' filename
特殊符号
符号 | 解释说明 | 示例 |
---|---|---|
! | 对指定行以外的所有行应用命令* | sed '1,3!d' file.txt (保留1-3行,删除其余行) |
= | 打印当前行行号 | sed '=' file.txt (输出每行内容前先打印行号) |
~ | 步长选择,First~Step 表示从 First 行开始,每隔 Step 行处理一次 | sed '1~2d' file.txt (删除所有奇数行) |
& | 代表被替换的内容(在 s 命令中使用) | sed 's/[0-9]/[&]/g' file.txt (把所有数字用 [] 包裹) |
: | 定义标签,用于 b (跳转)和 t (条件跳转)命令* | sed ':loop; s/foo/bar/; t loop' file.txt (循环替换直到没有匹配) |
{} | 对单个地址或地址范围执行批量操作 | sed '/pattern/{s/foo/bar/; p}' file.txt (匹配 pattern 的行先替换再打印) |
+ | 地址范围加法运算(GNU sed 支持) | sed '1,+2d' file.txt (删除第1行及接下来的2行) |
分组
在 sed 中,分组是通过()
实现的,用于:
- 将多个字符视为一个整体
- 在替换时引用匹配的内容
- 实现复杂的模式匹配
基本语法
bash
# 基本分组语法 -r支持扩展正则
sed -r 's/(pattern)/\1/' filename
# 引用分组
\0 # 所有
\1 # 第一个分组
\2 # 第二个分组
...
\9 # 第九个分组
# 给数字加括号
echo "abc123def" | sed 's/([0-9]+)/(\1)/g'
# 输出: abc(123)def
# 格式化日期 (YYYYMMDD → YYYY-MM-DD)
echo "20231225" | sed -r 's/([0-9]{4})([0-9]{2})([0-9]{2})/\1-\2-\3/'
# 输出: 2023-12-25
# 批量文件重命名
[root@localhost demo]# ls
test-2025-05-22-abc-10.jpg test-2025-05-22-abc-2.jpg test-2025-05-22-abc-4.jpg test-2025-05-22-abc-6.jpg test-2025-05-22-abc-8.jpg
test-2025-05-22-abc-1.jpg test-2025-05-22-abc-3.jpg test-2025-05-22-abc-5.jpg test-2025-05-22-abc-7.jpg test-2025-05-22-abc-9.jpg
[root@localhost demo]# ls | sed -r 's/([a-z]{4})-([0-9]{4})-([0-9]{2})-([0-9]{2})-([a-z]{3})-([0-9]{1,3})(.jpg)/mv \0 \1_\5_\6\7/'
mv test-2025-05-22-abc-10.jpg test_abc_10.jpg
mv test-2025-05-22-abc-1.jpg test_abc_1.jpg
mv test-2025-05-22-abc-2.jpg test_abc_2.jpg
mv test-2025-05-22-abc-3.jpg test_abc_3.jpg
mv test-2025-05-22-abc-4.jpg test_abc_4.jpg
mv test-2025-05-22-abc-5.jpg test_abc_5.jpg
mv test-2025-05-22-abc-6.jpg test_abc_6.jpg
mv test-2025-05-22-abc-7.jpg test_abc_7.jpg
mv test-2025-05-22-abc-8.jpg test_abc_8.jpg
mv test-2025-05-22-abc-9.jpg test_abc_9.jpg
经典案例
打印匹配行
bash
sed -n '80,90p' app.log # 打印80到90行内容
sed -n '80p;90p' app.log # 打印80和90行内容
sed -n '/error/p' app.log # 打印匹配"error"行,不显示其它内容
sed -n '20,/error/p' app.log # 打印第20行到匹配的"error"行的内容
sed -n '20p;/error/p' app.log # 打印第20行和匹配的"error"行的内容
sed -n '/stop$/p' app.log # 打印以 "stop" 结尾的内容
sed -n '/INFO/p;/WARN/p' app.log # 打印匹配 "INFO" 和 "WARN" 行的内容
# 匹配ifconfig的第二行并匹配IP地址\1是打印第1个分组() 的值,\0打印所有
ifconfig | sed -n '2p' | sed -r 's/inet (.*) netmask (.*) broadcast (.*)/\1/'
替换匹配行内容
bash
sed 's/2023/2025/' app.log # 每行首个 "2023" 替换 "2025",结果打印终端,源数据未修改
sed 's/23/25/g' app.log # 所有 "23" 替换 "25"(全局替换),结果打印终端,源数据未修改
sed 's/apple/orange/g' app.log # 所有 "apple" 替换 "orange"(全局替换),结果打印终端上,源数据未修改
sed -i 's/2023/2025/' app.log # 每行首个 "2023" 替换 "2025",修改源文件内容,无结果输出
sed -i.bak 's/admin/demo/g' *.log # 批量替换所有日志中的 "admin" 为 "demo" 并备份
删除匹配内容
bash
sed -e '/^$/d' -e '/^#/d' config.conf # 删除空行和#开头行内容。(^$表示空行)
sed '/stop$/d' app.log # 删除以 "stop" 结尾的内容
sed '/error/d' app.log # 删除匹配 "error" 行余内容
sed '/ERROR/d;/FATAL/d' app.log # 删除匹配 "ERROR" 和 "FATAL" 行内容
在匹配行插入/追加文本
bash
sed 'x,yi aaaaaaaaaa' # 在x行到y行前增加内容(也可按字符操作sed '/字符1/,/字符2/i 内容')
sed 'x,ya aaaaaaaaaa' # 在x行到y行后增加内容
sed '$ aaaaaaaaaa' # 在最后一行增加内容
sed 'x,ys#^#内容#' # 把文件x行到y行的行首内容替换
sed 'x,ys#$#内容#' # 把文件x行到y行的行尾内容替换
sed 's/user root/#&/' config.conf # 在匹配内容前插入,在同一行内
sed 's/user root/&#/' config.conf # 在匹配内容后插入,在同一行内
sed '/user root/i\# 这是注释' config.conf # 在匹配 "user root" 行的上一行插入 "# 这是注释" ,未修改源数据
sed '/pattern/a\ADDED_TEXT' config.conf # 在匹配 "pattern" 的下一行追加 "ADDED_TEXT",未修改源数据
直接修改原文件(危险操作!)
bash
sed -i.bak 's/old/new/g' config.conf # 先备份原文件
正则
在 JSON 文件中的 "port": 值后插入 8080
bash
# config.json
{
"service": {
"port": 80,
"host": "localhost"
}
}
sed -E 's/("port": )[0-9]+/&80/' config.json # -E开启正则匹配
注意事项
-i
选项会直接修改源文件,重要文件操作前建议先测试或备份- 特殊字符(如
/
&
)在替换模式中需要转义 - 默认输出所有处理后的行,使用
-n
配合p
命令可控制输出 - 不同版本sed存在差异(如MacOS使用BSD版本,Linux使用GNU版本)
- 复杂文本处理建议结合awk或其他工具使用
awk - 结构化文本处理
awk 是一种强大的文本处理工具,主要用于模式扫描和处理语言。它特别适合处理结构化文本数据,如日志文件、CSV文件等。awk 可以执行复杂的文本分析、数据提取和报告生成任务。
语法格式
bash
awk '模式 {动作}' 文件
awk [选项] '脚本命令' 输入文件 # 直接执行脚本
awk [选项] -f 脚本文件 输入文件 # 从文件读取脚本
脚本结构
bash
awk 'BEGIN{预处理} /模式/{动作} END{收尾处理}' 文件名
- BEGIN 块:处理输入数据前执行,用于初始化变量或打印表头(如
BEGIN{FS=","}
设置分隔符) - 模式+动作块:逐行处理数据,匹配模式(正则/条件)时执行动作(如
$3 > 50 {print}
) - END 块:处理完所有数据后执行,常用于输出统计结果(如
END{print "总和:", sum}
)
选项
选项 | 功能说明 | 典型用法示例 |
---|---|---|
-F fs | 指定字段分隔符(支持正则表达式) | awk -F':' '{print $1}' 按冒号分列 |
-v var=value | 定义外部变量传递给 awk | awk -v OFS="," '{print $1,$3}' 设置输出分隔符1****9 |
-f script | 从脚本文件读取命令 | awk -f script.awk data.txt 执行复杂脚本 |
!~ | 不匹配运算符 | awk '$1 !~ "nobody"' file.txt |
-m | 设置字段/记录数限制 | awk -mf 1000 限制最大字段数1 |
--debug | 调试模式显示处理过程 | awk --debug '/error/{print}' log.txt |
-n | 识别八进制/十六进制数据 | awk -n '{print $1+1}' 处理特殊进制数值 |
数组
AWK 的数组是非常强大的数据结构,它实际上是关联数组(类似于其他语言的字典或哈希表)。下面我将从基础到高级全面讲解 AWK 数组的使用方法。
数组定义与赋值
bash
# 定义数组并赋值
arr["name"] = "Linux"
arr["version"] = 5.15
arr[1] = "first"
arr[2] = "second"
# 访问数组元素
print arr["name"] # 输出: Linux
print arr[2] # 输出: second
# 检查元素是否存在
if ("version" in arr) {
print "版本存在:", arr["version"]
}
数组遍历
bash
# 基本遍历方法
for (key in arr) {
print key " => " arr[key]
}
# 控制遍历顺序 (GNU awk 特有)
# 按索引字符串排序
PROCINFO["sorted_in"] = "@ind_str_asc"
for (i in arr) { print i, arr[i] }
# 按索引数值排序
PROCINFO["sorted_in"] = "@ind_num_asc"
for (i in arr) { print i, arr[i] }
数组操作
bash
delete arr["version"] # 删除单个元素
delete arr # 删除整个数组
print length(arr) # 获取数组元素个数
# 多维数组模拟
# AWK 不支持真正的多维数组,但可以通过 SUBSEP 分隔符模拟:
multi["row1", "col1"] = "data1"
multi["row1", "col2"] = "data2"
for (k in multi) {
split(k, sep, SUBSEP)
print sep[1], sep[2], multi[k]
}
内置变量
变量名 | 描述 | 数据类型 | 示例 |
---|---|---|---|
$0 | 所有列的数据 | 列表 | awk '{print $0}' file |
$1-$9 | 第1-9列的数据 | 字符串 | awk 'print $1,$2' file |
NR | 当前行号 | 数字 | awk '{print NR, $0}' file |
NF | 当前列的总数 | 数字 | awk '{print $NF}' file |
FS | 输入列分隔符 | 字符串 | awk 'BEGIN{FS=":"} {print $1}' |
OFS | 输出列分隔符 | 字符串 | awk 'BEGIN{OFS=";"} {$1=$1; print}' |
RS | 输入行分隔符 | 字符串 | awk 'BEGIN{RS=";"} {print}' |
ORS | 输出行分隔符 | 字符串 | awk 'BEGIN{ORS="\n\n"} {print}' |
FILENAME | 当前输入文件名 | 字符串 | awk '{print FILENAME, NR, $0}' file |
FNR | 当前文件中的记录号 | 数字 | awk '{print FNR, $0}' file1 file2 |
ARGC | 命令行参数个数 | 数字 | awk 'BEGIN{print ARGC}' |
ARGV | 命令行参数数组 | 数组 | awk 'BEGIN{print ARGV[1]}' file |
ENVIRON | 环境变量数组 | 数组 | awk 'BEGIN{print ENVIRON["HOME"]}' |
CONVFMT | 数字转换格式 | 字符串 | awk 'BEGIN{CONVFMT="%d"; print 12.345}' |
OFMT | 数字输出格式 | 字符串 | awk 'BEGIN{OFMT="%.2f"; print 12.345}' |
SUBSEP | 多维数组下标分隔符 | 字符串 | awk 'BEGIN{SUBSEP=":"; a[1,2]=3}' |
经典案例
按行操作与匹配
bash
awk 'NR>5' app.log # 打印从第6行到末尾
awk 'NR>=5 && NR<=8' app.log # 打印第5行到第8行
awk 'NR<=5 || NR>=8' app.log # 打印第1到5行和第8行到末尾
awk '/INFO/' app.log # 打印所有匹配 "INFO" 行内容
awk '/INFO/;/WARN/' app.log # 打印所有匹配 "INFO" 和 "WARN" 行内容
选项案例
bash
# 使用冒号作为分隔符处理/etc/passwd
awk -F: '{print $1, $7}' /etc/passwd
# 使用多个字符作为分隔符
awk -F'[:/]' '{print $1, $4}' /etc/passwd
# 使用正则表达式作为分隔符(分隔数字和非数字)
awk -F'[0-9]+' '{print $1, $2}' data.txt
# 定义搜索阈值变量
awk -v threshold=100 '$3 > threshold' data.txt
# 定义多个变量
awk -v name="John" -v age=30 'BEGIN{print name, age}'
# 使用系统变量
awk -v user="$USER" 'BEGIN{print "Current user:", user}'
# 从脚本文件执行
awk -f process.awk data.txt
# 组合多个脚本文件
awk -f header.awk -f body.awk -f footer.awk input.txt
# 组合使用-F和-v选项
awk -F: -v OFS="\t" '{print $1, $3, $6}' /etc/passwd
# 同时使用脚本文件和命令行程序
awk -f header.awk -v date="$(date)" 'END{print "Processed on:", date}' data.txt
# 处理多文件时使用不同分隔符
awk -F: '{print FILENAME, $1}' /etc/passwd -F, '{print FILENAME, $2}' data.csv
注意事项
- 选项顺序很重要,后面的选项可能覆盖前面的设置
- -F选项会影响所有后续的文件处理,除非被新的-F覆盖
- -v定义的变量在BEGIN块中即可使用
- 使用--可以确保后续参数不被解释为选项
- 不同版本的awk(GNU awk、mawk等)支持的选项可能有差异
- 复杂脚本建议使用-f选项从文件加载,提高可维护性
预定义变量案例
bash
# FS - 输入列分隔符(等效于-F选项)
awk 'BEGIN{FS=","} {print $1, $3}' data.csv
# OFS - 输出列分隔符(格式化输出)
awk 'BEGIN{OFS=" | "} {$1=$1; print}' data.txt
# RS - 自定义行分隔符(处理段落)
awk 'BEGIN{RS=""; FS="\n"} {print "段落", NR, "有", NF, "行"}' doc.txt
# ORS - 输出行分隔符(改变换行符)
awk 'BEGIN{ORS="\n\n"} {print $0}' items.txt
# OFMT - 数字输出格式
awk 'BEGIN{OFMT="%.2f"} {sum+=$3} END{print "总和:", sum}' sales.txt
# CONVFMT - 数字转换格式
awk 'BEGIN{CONVFMT="%d"} {print $1/3}' numbers.txt
# NR - 总记录数统计
awk 'END{print "Total records:", NR}' access.log
# FNR - 文件内记录数(处理多文件时特别有用)
awk '{print FILENAME, FNR, $1}' file1.txt file2.txt
# NF - 字段数量检查
awk 'NF != 6 {print "Line", NR, "has", NF, "fields"}' data.csv
注意事项
- 内置变量名区分大小写(全部大写)
- 修改
$0
或字段后会重新计算NF
NR
对所有文件累计计数,FNR
对每个文件单独计数ENVIRON
数组访问的环境变量名需大写- 某些变量(如
PROCINFO
)是GNU awk特有 - 在BEGIN块中修改变量最安全(如
FS
、OFS
等) SUBSEP
默认值是\034
(不可见字符)
数组案例
基本练习
bash
cat >> domain.log << EOF
http://www.yunjisuan.org/index.html 10
http://www.yunjisuan.org/index.html 10
http://post.yunjisuan.org/index.html 15
http://mp3.yunjisuan.org/index.html 50
http://www.yunjisuan.org/index.html 10
http://post.yunjisuan.org/index.html 15
EOF
# 统计每个域名请求次数和域名总的请求流量
awk -F "[/. ]+" '{
DOMAIN[$2]+=1;
TOTAL[$2]+=$NF
}
END{
for (d in DOMAIN) {
print "域名:" d "\t请求数:" DOMAIN[d] "\t流量:" TOTAL[d]
}
}' domain.log
# 或者
awk -F "[/. ]+" '{
DOMAIN[$2]["count"]+=1;
DOMAIN[$2]["total"]+=$NF
}
END{
for (i in DOMAIN) {
print "域名:"i "\t请求数:"DOMAIN[i]["count"] "\t流量:"DOMAIN[i]["total"]
}
}' domain.log
Nginx日志统计
bash
# 格式如下
101.226.61.184 - - [22/Nov/2015:11:02:00 +0800] "GET /mobile/sea-modules/gallery/zepto/1.1.3/zepto.js HTTP/1.1" 200 24662 "http://m.oldboyedu.com.cn/mobile/theme/old/home/index.html" "Mozilla/5.0 (Linux; U; Android 5.1.1; zh-cn; HUAWEI CRR-UL00 Build/HUAWEICRR-UL00) AppleWebKit/533.1 (KHTML, like Gecko)Version/4.0 MQQBrowser/5.4 TBS/025478 Mobile Safari/533.1 MicroMessenger/6.3.7.51_rbb7fa12.660 NetType/3gnet Language/zh_CN"
# 统计每个ip的请求次数、流量、每个状态码的次数
awk '
{
DOMAIN[$1]["ip"]++;
DOMAIN[$1]["code"][$9]++;
DOMAIN[$1]["kbs"]+=$10
}
END{
# 输出表头(%-20s表示向左对齐宽度20)
printf "│%-20s│%-10s│%-15s│%-20s│\n", "IP", "请求数", "流量(MB)", "状态码"
for (i in DOMAIN){
## 保留小数点后两位
kbs=sprintf("%.2f", DOMAIN[i]["kbs"]/1024/1024)
codes = ""
# 拼接状态码为字符串
for (c in DOMAIN[i]["code"]) {
codes = codes sprintf("%s:%d ", c, DOMAIN[i]["code"][c])
}
printf "│%-20s│%-10s│%-15s│%-20s│\n",i,DOMAIN[i]["ip"],kbs,codes
}
}' access.log | column -t
# column -t 对齐
注意事项
- awk 默认使用空格和制表符作为字段分隔符
- 内置变量如 NR(行号)、NF(字段数)、FS(字段分隔符)等需要大写
- 在 BEGIN 块中执行的代码在处理任何输入前运行
- 在 END 块中执行的代码在处理完所有输入后运行
- awk 支持正则表达式匹配,可以使用 ~ 和 !~ 运算符
- 复杂的awk脚本建议保存到单独文件中使用-f选项调用
- 不同版本的awk(GNU awk、nawk等)可能有细微差异
实际应用场景
grep
命令:用于搜索文件中的文本。例如,grep '^abc' file.txt
会搜索file.txt
中所有以abc
开头的行。sed
命令:用于文本替换。例如,sed 's/a/b/g' file.txt
会将file.txt
中所有的a
替换为b
。awk
命令:用于文本处理。例如,awk '/^abc/ {print $0}' file.txt
会打印file.txt
中所有以abc
开头的行。