AWK命令使用
AWK是一种功能强大的文本处理语言,擅长数据分析和报告生成。它逐行处理文本,自动分割字段,支持条件判断和循环操作,适用于日志分析、数据提取等场景。AWK包含三大核心块:BEGIN(预处理)、主块(逐行处理)和END(汇总)。通过内置变量(如NR、NF)和自定义分隔符(FS/RS),可灵活处理不同格式的文本数据。AWK支持命令行直接执行、多行脚本或脚本文件三种运行方式,并提供了丰富的条件判断和正则匹
AWK命令使用
AWK 是一种文本处理语言,以处理数据、生成报告见长,兼具编程语言的灵活性和命令行工具的便捷性。它的核心优势是:按行处理文本、自动分割字段、支持条件判断和循环,适用于日志分析、数据提取、格式转换等场景。
一、AWK 基础入门
1.1 什么是 AWK?
- 命名来源:取自三位创始人姓氏首字母(Aho、Weinberger、Kernighan)。
- 定位:文本处理工具 + 解释型编程语言。
- 核心思想:逐行读取文本,按指定规则分割字段,执行自定义操作。
- 特点:
- 无需编译,直接运行;
- 自动处理字段分割(默认空格/制表符);
- 内置变量、函数丰富,支持正则表达式;
- 可处理大文件(逐行读取,内存占用低)。
1.2 安装与运行方式
AWK 是类 Unix 系统(Linux、macOS)内置工具,Windows 需安装 Git Bash、WSL 或 Cygwin。
运行方式(3种核心形式)
(1)命令行直接执行(单行命令)
格式:
awk '条件{操作}' 文件名
示例:读取 test.txt,打印所有行
# 准备测试文件
cat > test.txt << EOF
Alice 25 Engineer
Bob 30 Designer
Charlie 28 ProductManager
EOF
# 执行AWK命令
awk '{print}' test.txt
执行结果:
Alice 25 Engineer
Bob 30 Designer
Charlie 28 ProductManager
简化写法(1 表示条件为真,默认执行 print):
awk 1 test.txt
执行结果:与上面完全一致。
(2)多行脚本(适合复杂逻辑)
格式:
awk '
条件1 {操作1}
条件2 {操作2}
' 文件名
示例:打印行号和内容
awk '
{print NR ": " $0} # NR 是内置变量,代表行号;$0 代表整行内容
' test.txt
执行结果:
1: Alice 25 Engineer
2: Bob 30 Designer
3: Charlie 28 ProductManager
(3)脚本文件(复用性强)
将 AWK 代码写入 .awk 文件,通过 -f 参数执行:
# 1. 创建脚本文件 script.awk
cat > script.awk << EOF
{print "行" NR ": " $0}
EOF
# 2. 执行脚本
awk -f script.awk test.txt
执行结果:
行1: Alice 25 Engineer
行2: Bob 30 Designer
行3: Charlie 28 ProductManager
1.3 核心概念:字段、记录与内置变量
AWK 处理文本时,会自动将数据拆分为「记录」和「字段」:
- 记录(Record):默认以「换行符」分隔,即一行是一条记录(对应变量
$0)。 - 字段(Field):默认以「空格/制表符」分隔,字段编号从
$1开始($1=第1字段,$2=第2字段,…,$NF=最后一个字段)。
常用内置变量(必背)
| 变量名 | 含义 | 示例 |
|---|---|---|
$0 |
整行文本(当前记录) | print $0 打印整行 |
$n |
第 n 个字段(n≥1) | $3 表示第3个字段 |
NF |
当前行的字段总数 | print NF 打印每行字段数 |
NR |
已读取的总记录数(行号) | print NR 打印当前行号 |
FNR |
每个文件的独立行号(多文件处理) | 处理2个文件时,各文件行号重新计数 |
FS |
字段分隔符(默认:空格/制表符) | FS="," 以逗号分隔字段 |
OFS |
输出字段分隔符(默认:空格) | `OFS=" |
RS |
记录分隔符(默认:换行符 \n) |
RS=";" 以分号分隔记录 |
ORS |
输出记录分隔符(默认:换行符) | ORS="\n---\n" 每行输出后加分隔线 |
FILENAME |
当前处理的文件名 | 多文件处理时标识文件 |
示例:使用内置变量处理文本
awk '{print "行" NR ", 姓名:" $1 ", 年龄:" $2 ", 职业:" $3 ", 字段数:" NF}' test.txt
执行结果:
行1, 姓名:Alice, 年龄:25, 职业:Engineer, 字段数:3
行2, 姓名:Bob, 年龄:30, 职业:Designer, 字段数:3
行3, 姓名:Charlie, 年龄:28, 职业:ProductManager, 字段数:3
二、字段分隔符与记录分隔符(FS/RS)
默认分隔符可能无法满足需求(如 CSV 文件、自定义格式文本),需手动指定 FS(字段分隔符)或 RS(记录分隔符)。
2.1 指定字段分隔符(FS)
方式1:通过 -F 参数(命令行快捷方式)
格式:awk -F "分隔符" '操作' 文件名
示例1:处理 CSV 文件(逗号分隔)
# 准备CSV文件
cat > data.csv << EOF
ID,Name,Age,City
1,Alice,25,NewYork
2,Bob,30,London
3,Charlie,28,Paris
EOF
# 提取姓名和城市
awk -F "," '{print $2 "\t" $4}' data.csv
执行结果:
Name City
Alice NewYork
Bob London
Charlie Paris
示例2:多字符分隔符(如 ::)
# 准备测试文件
cat > log.txt << EOF
2025-12-01::Alice::Login
2025-12-01::Bob::Logout
EOF
# 提取时间和操作
awk -F "::" '{print $1 " -> " $3}' log.txt
执行结果:
2025-12-01 -> Login
2025-12-01 -> Logout
方式2:在脚本中设置 FS 变量
awk 'BEGIN{FS=","} {print $2 "\t" $4}' data.csv
执行结果:与示例1完全一致。
2.2 指定记录分隔符(RS)
默认一条记录是一行(RS="\n"),可自定义为其他字符(如分号、空格)。
示例:处理以分号分隔的记录
# 准备测试文件
cat > record.txt << EOF
Alice:25:Engineer;Bob:30:Designer;Charlie:28:ProductManager
EOF
# 以分号分隔记录,冒号分隔字段
awk 'BEGIN{RS=";"; FS=":"} {print "姓名:" $1 ", 年龄:" $2}' record.txt
执行结果:
姓名:Alice, 年龄:25
姓名:Bob, 年龄:30
姓名:Charlie, 年龄:28
三、AWK 三大核心块(BEGIN/主块/END)
AWK 脚本由「可选块」组成,执行顺序固定:
BEGIN{}:读取文本前执行(仅1次),用于初始化变量、设置分隔符、打印表头;- 「主块」(无关键字):逐行读取文本时执行(每行1次),用于处理数据(条件判断、字段提取等);
END{}:读取所有文本后执行(仅1次),用于汇总结果(统计、计算总数等)。
格式:
BEGIN {
# 初始化操作(如设置FS、打印表头)
}
{
# 逐行处理逻辑(主逻辑)
}
END {
# 汇总操作(如统计总数、计算平均值)
}
实战案例:统计学生成绩
# 准备成绩文件
cat > scores.txt << EOF
Alice 85 92 78
Bob 76 88 90
Charlie 95 89 92
David 68 72 65
EOF
# 执行统计脚本
awk '
BEGIN {
FS=" "; # 字段分隔符(默认空格,可省略)
OFS="\t"; # 输出字段分隔符(制表符)
print "姓名", "语文", "数学", "英语", "总分"; # 表头
print "----------------------------------------";
total_sum = 0; # 初始化全班总分
count = 0; # 初始化学生人数
}
{
sum = $2 + $3 + $4; # 计算每人总分
print $1, $2, $3, $4, sum;
total_sum += sum; # 累加全班总分
count++; # 累加学生人数
}
END {
print "----------------------------------------";
avg = total_sum / (count * 3); # 计算平均分(3科)
printf "全班平均分: %.2f\n", avg; # 格式化输出(保留2位小数)
print "学生总数: " count;
}
' scores.txt
执行结果:
姓名 语文 数学 英语 总分
----------------------------------------
Alice 85 92 78 255
Bob 76 88 90 254
Charlie 95 89 92 276
David 68 72 65 205
----------------------------------------
全班平均分: 81.67
学生总数: 4
四、条件判断(if/else if/else)
AWK 支持标准的条件判断语法,可根据字段值、行号等筛选数据。
语法格式
{
if (条件1) {
操作1;
} else if (条件2) {
操作2;
} else {
操作3;
}
}
条件表达式支持的运算符
| 类型 | 运算符 | 示例 |
|---|---|---|
| 比较运算符 | ==(等于)、!=(不等于)、>(大于)、<(小于)、>=、<= |
$2 >= 90(第2字段≥90) |
| 逻辑运算符 | &&(与)、` |
|
| 正则匹配 | ~(匹配)、!~(不匹配) |
$1 ~ /^A/(姓名以A开头) |
实战案例
案例1:筛选特定行(行号条件)
awk 'NR >= 2 && NR <= 3 {print $1 " 的成绩: " $2 " " $3 " " $4}' scores.txt
执行结果:
Bob 的成绩: 76 88 90
Charlie 的成绩: 95 89 92
案例2:筛选符合条件的字段(数值条件)
awk '
{
sum = $2 + $3 + $4;
if ($3 >= 90 || sum >= 260) {
print $1 ": 数学=" $3 ", 总分=" sum;
}
}
' scores.txt
执行结果:
Alice: 数学=92, 总分=255
Charlie: 数学=89, 总分=276
案例3:正则匹配筛选(文本条件)
awk '$1 ~ /^C/ || $3 == "Engineer" {print $0}' test.txt
执行结果:
Alice 25 Engineer
Charlie 28 ProductManager
五、循环结构(for/while/do-while)
AWK 支持循环结构,可用于批量处理字段、重复执行操作等。
5.1 for 循环(最常用)
格式1:普通循环(类似 C 语言)
awk 'BEGIN{for (i=1; i<=5; i++) print i}'
执行结果:
1
2
3
4
5
格式2:遍历字段(for-in 循环)
awk '{
print "第" NR "行字段:";
for (i=1; i<=NF; i++) { # i 从1到 NF(字段总数)
print " 字段" i ": " $i;
}
}' test.txt
执行结果:
第1行字段:
字段1: Alice
字段2: 25
字段3: Engineer
第2行字段:
字段1: Bob
字段2: 30
字段3: Designer
第3行字段:
字段1: Charlie
字段2: 28
字段3: ProductManager
5.2 while 循环
awk 'BEGIN{
i=1; sum=0;
while (i<=10) {
sum += i;
i++;
}
print "1-10累加和:" sum;
}'
执行结果:
1-10累加和:55
5.3 do-while 循环(先执行后判断)
awk 'BEGIN{
i=11; sum=0;
do {
sum += i;
i++;
} while (i<=10); # 条件不满足,但仍执行1次
print sum; # 输出 11
}'
执行结果:
11
六、数组(关联数组)
AWK 只有「关联数组」(类似 Python 字典、Java Map),索引可以是字符串或数字,无需预先声明长度,动态扩容。
6.1 数组的基本操作(增删改查)
awk 'BEGIN{
# 字符串索引
user["name"] = "Alice";
user["age"] = 25;
# 数字索引
score[1] = 85;
score[2] = 92;
# 查:打印数组值
print user["name"], user["age"]; # Alice 25
print score[1], score[2]; # 85 92
# 改:修改值
user["age"] = 26;
print user["age"]; # 26
# 删:删除元素(用 delete 关键字)
delete score[2];
print score[2]; # 空值(数组中无该索引)
}'
执行结果:
Alice 25
85 92
26
遍历数组(for-in 循环)
awk 'BEGIN{
fruit["a"] = "apple";
fruit["b"] = "banana";
fruit["c"] = "cherry";
for (key in fruit) {
print key ": " fruit[key];
}
}'
执行结果(顺序可能不同):
a: apple
b: banana
c: cherry
6.2 实战案例:统计文本中单词出现次数
# 准备单词文件
cat > words.txt << EOF
Hello world Hello AWK world Python AWK
EOF
# 执行统计脚本
awk '
BEGIN{
FS=" "; # 按空格分割单词
IGNORECASE=1; # 忽略大小写(GNU AWK 扩展)
}
{
for (i=1; i<=NF; i++) {
word = $i;
if (word != "") { # 排除空字符串(避免多余空格)
count[word]++; # 统计次数
}
}
}
END{
print "单词出现次数:";
for (w in count) {
print w ": " count[w];
}
}' words.txt
执行结果:
单词出现次数:
Hello: 2
world: 2
AWK: 2
Python: 1
七、正则表达式与模式匹配
AWK 内置强大的正则表达式支持,可用于字段匹配、行筛选、文本替换等。
7.1 正则匹配运算符
| 运算符 | 含义 | 示例 |
|---|---|---|
~ |
字段匹配正则 | $1 ~ /^A/(第1字段以A开头) |
!~ |
字段不匹配正则 | $3 !~ /er$/(第3字段不以er结尾) |
7.2 常用正则表达式元字符
| 元字符 | 含义 | 示例 |
|---|---|---|
^ |
行首 | /^Alice/(行以Alice开头) |
$ |
行尾 | /Engineer$/(行以Engineer结尾) |
. |
任意单个字符 | /A..e/(匹配A开头、e结尾的4个字符,如Alice) |
* |
前一个字符重复0次或多次 | /a*/(匹配任意个a,包括空) |
+ |
前一个字符重复1次或多次 | /a+/(匹配至少1个a) |
? |
前一个字符重复0次或1次 | /a?/(匹配0或1个a) |
[] |
字符集合 | /[0-9]/(匹配数字)、/[A-Za-z]/(匹配字母) |
[^] |
否定字符集合 | /[^0-9]/(匹配非数字) |
| ` | ` | 逻辑或 |
() |
分组 | `/A(li |
7.3 实战案例
案例1:筛选包含数字的行
awk '/[0-9]/ {print $0}' test.txt
执行结果:
Alice 25 Engineer
Bob 30 Designer
Charlie 28 ProductManager
案例2:提取日志中的IP地址(假设IP格式为xxx.xxx.xxx.xxx)
# 准备日志文件
cat > access.log << EOF
192.168.1.1 - [01/Dec/2025:10:00:00 +0800] "GET / HTTP/1.1" 200
10.0.0.1 - [01/Dec/2025:10:01:00 +0800] "POST /login HTTP/1.1" 302
EOF
# 提取IP
awk '{if ($1 ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/) print "IP: " $1}' access.log
执行结果:
IP: 192.168.1.1
IP: 10.0.0.1
案例3:文本替换(gsub/sub 函数)
awk '
{
sub(/25/, "二十五"); # 替换第一次出现的25
gsub(/30/, "三十"); # 替换所有出现的30
print $0;
}
' test.txt
执行结果:
Alice 二十五 Engineer
Bob 三十 Designer
Charlie 28 ProductManager
八、常用内置函数
AWK 提供大量内置函数,覆盖字符串处理、数值计算、时间处理等场景,以下是高频函数:
8.1 字符串函数
| 函数 | 功能 | 示例 |
|---|---|---|
length(s) |
返回字符串s的长度(默认$0) | length($1)(姓名长度) |
substr(s, start, len) |
截取字符串s(从start位置开始,取len个字符) | substr($1, 1, 3)(取姓名前3个字符) |
index(s, t) |
返回t在s中首次出现的位置(未找到返回0) | index($3, "er")(职业中er的位置) |
tolower(s) |
转换为小写 | tolower($1)(姓名小写) |
toupper(s) |
转换为大写 | toupper($3)(职业大写) |
split(s, arr, sep) |
将s按sep分割,存入数组arr | split($0, arr, " ")(分割整行到数组) |
示例:字符串函数实战
awk '{
name = $1;
printf "姓名:%s, 长度:%d, 前2字符:%s, 大写:%s\n",
name, length(name), substr(name,1,2), toupper(name);
}' test.txt
执行结果:
姓名:Alice, 长度:5, 前2字符:Al, 大写:ALICE
姓名:Bob, 长度:3, 前2字符:Bo, 大写:BOB
姓名:Charlie, 长度:7, 前2字符:Ch, 大写:CHARLIE
8.2 数值函数
| 函数 | 功能 | 示例 |
|---|---|---|
int(n) |
取整(丢弃小数部分) | int(85.9) → 85 |
sqrt(n) |
平方根 | sqrt(100) → 10 |
rand() |
生成0-1之间的随机数 | rand() → 0.345(每次执行不同) |
srand(n) |
设置随机数种子(n为时间戳时每次不同) | srand()(默认用时间戳) |
max(a,b) |
取最大值(GNU AWK) | max($2,$3)(语文和数学的最高分) |
min(a,b) |
取最小值(GNU AWK) | min($2,$3)(语文和数学的最低分) |
示例:生成3个1-100的随机数
awk 'BEGIN{
srand(); # 初始化随机数种子
for (i=1; i<=3; i++) {
print int(rand()*100) + 1; # rand()*100 → 0-99.99,+1后1-100
}
}'
执行结果(每次不同,示例):
45
89
12
8.3 时间函数(GNU AWK 扩展)
| 函数 | 功能 | 示例 |
|---|---|---|
mktime("YYYY MM DD HH MM SS") |
将时间字符串转换为时间戳(秒数) | mktime("2025 12 01 10 00 00") |
strftime(format, timestamp) |
将时间戳转换为指定格式的字符串 | strftime("%Y-%m-%d %H:%M:%S") |
示例:打印当前时间和指定时间戳的日期
awk 'BEGIN{
# 当前时间
now = systime(); # 获取当前时间戳
print "当前时间:" strftime("%Y-%m-%d %H:%M:%S", now);
# 转换指定时间戳(2025-12-01 00:00:00)
ts = mktime("2025 12 01 00 00 00");
print "指定时间:" strftime("%Y年%m月%d日", ts);
}'
执行结果(时间随实际运行时刻变化):
当前时间:2025-12-04 16:20:35
指定时间:2025年12月01日
九、高级实战场景
场景1:分析Nginx访问日志(统计Top10 IP)
# 准备Nginx日志
cat > nginx.log << EOF
192.168.1.1 [01/Dec/2025:10:00:00] GET / 200
10.0.0.1 [01/Dec/2025:10:01:00] POST /login 302
192.168.1.1 [01/Dec/2025:10:02:00] GET /about 200
192.168.1.2 [01/Dec/2025:10:03:00] GET / 200
192.168.1.1 [01/Dec/2025:10:04:00] GET /contact 200
EOF
# 统计Top10 IP
awk '
{
ip = $1;
count[ip]++; # 统计每个IP的访问次数
}
END{
# 将数组转为“IP:次数”字符串,存入临时数组
for (i in count) {
arr[++n] = i ":" count[i];
}
# 排序(按次数降序,GNU AWK 的 asorti 支持自定义排序)
asorti(arr, sorted, "@val_num_desc");
# 打印前10
print "Top10 访问IP:";
for (i=1; i<=10 && i<=n; i++) {
split(arr[i], tmp, ":");
print i ". " tmp[1] " → " tmp[2] "次";
}
}' nginx.log
执行结果:
Top10 访问IP:
1. 192.168.1.1 → 3次
2. 10.0.0.1 → 1次
3. 192.168.1.2 → 1次
场景2:格式化JSON输出(将CSV转为JSON)
# 复用之前的data.csv文件
awk '
BEGIN{
FS=",";
print "["; # JSON数组开始
first=1; # 标记是否为第一个元素(避免末尾多余逗号)
}
NR>1{ # 跳过表头(NR>1)
if (!first) print ","; # 非第一个元素前加逗号
printf ' {"ID":%d, "Name":"%s", "Age":%d}', $1, $2, $3;
first=0;
}
END{
print "\n]"; # JSON数组结束
}' data.csv
执行结果:
[
{"ID":1, "Name":"Alice", "Age":25},
{"ID":2, "Name":"Bob", "Age":30},
{"ID":3, "Name":"Charlie", "Age":28}
]
场景3:计算日志中各状态码的占比
# 复用之前的nginx.log文件
awk '
{
code = $5; # 状态码在第5字段
total++; # 总请求数
code_count[code]++; # 统计各状态码次数
}
END{
print "状态码统计(总请求数:" total "):";
for (c in code_count) {
ratio = (code_count[c] / total) * 100;
printf " %s: %d次 (%.2f%%)\n", c, code_count[c], ratio;
}
}' nginx.log
执行结果:
状态码统计(总请求数:5):
200: 4次 (80.00%)
302: 1次 (20.00%)
十、AWK 高频场景速查表(按使用频率排序)
| 场景 | 核心命令/脚本 | 说明 |
|---|---|---|
| 打印指定字段 | awk '{print $1, $3}' file |
打印第1、3字段(默认空格分隔) |
| 按行号筛选 | awk 'NR==5 {print $0}' file |
打印第5行;NR>=2 && NR<=10 打印2-10行 |
| 按字段值筛选 | awk '$2>80 {print $0}' file |
第2字段>80时打印整行 |
| CSV文件处理 | awk -F "," '{print $2}' data.csv |
以逗号分隔,提取第2字段 |
| 统计行数 | awk 'END{print NR}' file |
等价于 wc -l file,更高效 |
| 统计字段数 | awk '{print NF}' file |
打印每行的字段总数 |
| 文本替换 | awk '{gsub(/old/, "new"); print $0}' file |
替换所有old为new |
| 单词计数 | awk '{for(i=1;i<=NF;i++) count[$i]++} END{for(w in count) print w,count[w]}' file |
统计每个单词出现次数 |
| 计算总和/平均值 | awk '{sum+=$2} END{print "总和:" sum, "平均值:" sum/NR}' file |
计算第2字段的总和与平均值 |
| 筛选含指定字符串的行 | awk '/error/ {print $0}' log.txt |
打印包含error的行(正则匹配) |
| 提取IP地址 | awk '$1 ~ /^([0-9]{1,3}\.){3}[0-9]{1,3}$/ {print $1}' log.txt |
从日志中提取IP格式的字段 |
| 多文件处理 | awk '{print FILENAME, NR, $0}' file1 file2 |
打印文件名、行号、内容 |
| 格式化输出 | awk '{printf "姓名:%-10s 年龄:%d\n", $1, $2}' file |
左对齐姓名(占10字符),年龄数字格式 |
| 排除空行 | awk 'NF>0 {print $0}' file |
仅打印非空行(空行NF=0) |
| 按分隔符拼接字段 | `awk 'BEGIN{OFS=" | "} {print $1, $2, $3}’ file` |
十一、常见问题与注意事项
- 字段分隔符问题:如果文本字段分隔符是多个空格/制表符,AWK 会自动合并为一个分隔符(默认行为),无需额外处理。
- 空行处理:空行的
NF=0,可通过NF>0筛选非空行:awk 'NF>0 {print $0}' file。 - 变量作用域:AWK 中变量默认是全局的,若需局部变量,需在函数内用
var声明(GNU AWK 扩展)。 - 正则匹配转义:正则中的特殊字符(如
.、*)需转义(\.、\*),或用字符集合[]包裹。 - Windows 兼容性:Windows 命令行中,单引号
'需替换为双引号",且内部双引号需转义(\")。 - 浮点数精度:AWK 中数值默认是浮点数,比较时需注意精度问题(如
1.0 == 1为真,但0.1+0.2 == 0.3可能为假)。
十二、总结
AWK 的核心能力是「逐行处理 + 字段分割 + 灵活的逻辑操作」,掌握以下关键点即可应对90%的文本处理场景:
- 内置变量(
$0、$n、NR、NF、FS); - 三大块(
BEGIN/主块/END); - 条件判断与循环;
- 正则匹配与字符串函数;
- 关联数组(统计场景必备)。
火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。
更多推荐
所有评论(0)