一、简介
1.1 什么是 awk
awk 是一种强大的文本处理语言,以三位创始人(Aho、Weinberger、Kernighan)的名字命名。它是一款功能强大的数据处理工具,可以完成:
文本扫描与处理
数据提取与转换
格式化输出
数值计算
生成报表
核心特点:
逐行处理文本
自动分割字段
支持正则表达式
完整的编程语言
丰富的内置函数
1.2 工作原理
┌─────────────────────────────────────────────────────────────────────┐
│ awk 工作流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 输入文件/标准输入 │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ BEGIN 块 │ ←── 执行前(可选) │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 读取一行 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ 分割字段 │ → │ $1, $2, $3...│ │
│ └──────┬──────┘ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 执行规则 │ ←── pattern { action } │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 还有行? │ ──Yes──► 读取下一行 │
│ └──────┬──────┘ │
│ │ No │
│ ▼ │
│ ┌─────────────┐ │
│ │ END 块 │ ←── 执行后(可选) │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
1.3 程序结构
# 基本结构
BEGIN {
# 初始化代码(在处理任何行之前执行)
}
pattern {
# 处理代码(对每行匹配的执行)
}
END {
# 收尾代码(处理完所有行后执行)
}
二、安装
2.1 各平台安装
Linux
# Debian/Ubuntu
sudo apt update
sudo apt install gawk
# CentOS/RHEL
sudo yum install gawk
# Alpine
apk add gawk
# Arch Linux
sudo pacman -S gawk
macOS
# macOS 自带 awk (BSD awk)
# 建议安装 GNU awk
brew install gawk
# 使用 gawk 命令
gawk 'pattern { action }' file.txt
# 或创建别名
alias awk='gawk'
Windows
# Coreutils
winget install Microsoft.Coreutils
# Git Bash 自带 awk
# 或通过 Chocolatey
choco install gawk
# 或使用 WSL
wsl awk
2.2 验证安装
awk --version
# 输出: GNU Awk 5.1.0
# 测试运行
echo "hello world" | awk '{print $0}'
# 输出: hello world
2.3 awk 实现版本
三、基础语法
3.1 命令格式
# 基本格式
awk 'pattern { action }' file.txt
# 多个规则
awk 'pattern1 { action1 } pattern2 { action2 }' file.txt
# 使用 -f 从文件读取
awk -f script.awk file.txt
# 指定字段分隔符
awk -F ',' '{print $1}' file.csv
# 多个选项
awk -F '\t' -v var=value 'pattern { action }' file.txt
3.2 常用选项
3.3 最简单的示例
# 打印每行
awk '{print}' file.txt
# 打印每行(同上)
awk '{print $0}' file.txt
# 打印第一列
awk '{print $1}' file.txt
# 打印第一和第三列
awk '{print $1, $3}' file.txt
# 打印行数
awk 'END {print NR}' file.txt
四、基础操作
4.1 字段访问
# 测试数据
echo "张三 25 北京" > test.txt
echo "李四 30 上海" >> test.txt
echo "王五 28 广州" >> test.txt
# $0 - 整行
awk '{print $0}' test.txt
# 输出:
# 张三 25 北京
# 李四 30 上海
# 王五 28 广州
# $1, $2, $3... - 第 N 个字段
awk '{print $1}' test.txt
# 输出:
# 张三
# 李四
# 王五
# 打印多个字段
awk '{print $1, $3}' test.txt
# 输出:
# 张三 北京
# 李四 上海
# 王五 广州
# NF - 字段数量
awk '{print NF}' test.txt
# 输出: 3 3 3
# $NF - 最后一个字段
awk '{print $NF}' test.txt
# 输出:
# 北京
# 上海
# 广州
# 倒数第二个字段
awk '{print $(NF-1)}' test.txt
# 输出:
# 25
# 30
# 28
4.2 字段分隔符
使用 -F 选项
# CSV 文件(逗号分隔)
awk -F ',' '{print $1}' data.csv
# TSV 文件(制表符分隔)
awk -F '\t' '{print $1}' data.tsv
# 多字符分隔符
awk -F '::' '{print $1}' data.txt
# 正则表达式分隔符
awk -F '[,;]' '{print $1}' data.txt # 逗号或分号
# 使用冒号(如 /etc/passwd)
awk -F ':' '{print $1}' /etc/passwd
在程序中设置
# 使用 FS 变量
awk 'BEGIN {FS=","} {print $1}' data.csv
# 使用正则表达式
awk 'BEGIN {FS="[ \t]+"} {print $1}' data.txt
# 输出分隔符
awk 'BEGIN {OFS=","} {print $1, $2}' data.txt
# 记录分隔符
awk 'BEGIN {RS=""} {print}' data.txt # 空行分隔记录
输出分隔符
# OFS - 输出字段分隔符(默认空格)
awk 'BEGIN {OFS=","} {print $1, $2, $3}' test.txt
# 输出: 张三,25,北京
# ORS - 输出记录分隔符(默认换行)
awk 'BEGIN {ORS="\n---\n"} {print}' test.txt
4.3 打印输出
print 命令
# 基本打印
awk '{print $1, $2}' file.txt
# 打印字符串
awk '{print "Name:", $1}' file.txt
# 打印表达式
awk '{print $1, $2+10}' file.txt
# 自定义分隔
awk '{print $1 "," $2 "," $3}' file.txt
# 或使用 OFS
awk 'BEGIN{OFS=","} {print $1, $2, $3}' file.txt
# 不换行
awk '{printf "%s ", $1} END{print ""}' file.txt
printf 格式化
# printf 格式
awk '{printf "Name: %-10s Age: %2d\n", $1, $2}' test.txt
# 输出:
# Name: 张三 Age: 25
# Name: 李四 Age: 30
# Name: 王五 Age: 28
# 格式说明符
# %s - 字符串
# %d - 整数
# %f - 浮点数
# %x - 十六进制
# %o - 八进制
# %c - 字符
# %e - 科学计数法
# 宽度与对齐
awk '{printf "%-10s %5d\n", $1, $2}' test.txt
# %-10s: 左对齐,宽度 10
# %5d: 右对齐,宽度 5
# 浮点数精度
awk '{printf "%.2f\n", $2/3}' test.txt
4.4 模式匹配
正则匹配
# 匹配包含 pattern 的行
awk '/pattern/ {print}' file.txt
# 匹配字段
awk '$1 ~ /^张/ {print}' test.txt
# 输出: 张三 25 北京
# 不匹配
awk '$1 !~ /^张/ {print}' test.txt
# 输出: 李四 30 上海
# 王五 28 广州
# 大小写不敏感
awk 'BEGIN{IGNORECASE=1} /pattern/ {print}' file.txt
比较表达式
# 数值比较
awk '$2 > 25 {print}' test.txt
# 输出: 李四 30 上海
# 王五 28 广州
awk '$2 >= 25 && $2 <= 30 {print}' test.txt
# 输出: 张三 25 北京
# 李四 30 上海
# 王五 28 广州
# 字符串比较
awk '$1 == "张三" {print}' test.txt
# 多条件
awk '$2 > 25 && $3 == "上海" {print}' test.txt
五、变量
5.1 内置变量
字段与记录
其他内置变量
使用示例
# NR - 行号
awk '{print NR, $0}' test.txt
# 输出:
# 1 张三 25 北京
# 2 李四 30 上海
# 3 王五 28 广州
# NF - 字段数
awk '{print NR, NF, $0}' test.txt
# 输出:
# 1 3 张三 25 北京
# 2 3 李四 30 上海
# 3 3 王五 28 广州
# FILENAME
awk '{print FILENAME, NR, $0}' test.txt
# 输出: test.txt 1 张三 25 北京 ...
# 多文件时 FNR vs NR
awk '{print FILENAME, FNR, NR}' test.txt test.txt
# FNR: 每个文件独立计数
# NR: 全局计数
# 环境变量
awk 'BEGIN {print ENVIRON["HOME"]}'
# 输出: /home/username
5.2 用户自定义变量
在命令行定义
# 使用 -v 选项
awk -v name="张三" 'BEGIN {print name}'
# 输出: 张三
# 多个变量
awk -v x=10 -v y=20 'BEGIN {print x+y}'
# 输出: 30
# 使用文件中的变量
awk -v file="data.txt" 'BEGIN {print file}'
在程序中定义
# 在 BEGIN 块中
awk 'BEGIN {count=0} {count++} END {print count}' test.txt
# 输出: 3
# 在处理过程中
awk '{total+=$2} END {print total}' test.txt
# 输出: 83 (25+30+28)
# 字符串变量
awk 'BEGIN {name="张三"; print "Hello", name}'
# 输出: Hello 张三
5.3 数组
关联数组
# 基本使用
awk '{
count[$1]++
} END {
for (name in count) {
print name, count[name]
}
}' test.txt
# 累计求和
awk '{
sum[$3] += $2
} END {
for (city in sum) {
print city, sum[city]
}
}' test.txt
删除数组元素
# 删除单个元素
awk '{
arr[$1] = $2
} END {
delete arr["张三"]
for (k in arr) print k, arr[k]
}' test.txt
# 检查元素是否存在
awk '{
arr[$1] = $2
} END {
if ("张三" in arr) {
print "张三存在"
}
}' test.txt
多维数组
# 模拟多维数组
awk '{
key = $1 SUBSEP $3
data[key] = $2
} END {
for (k in data) {
split(k, parts, SUBSEP)
print parts[1], parts[2], data[k]
}
}' test.txt
# 或使用字符串拼接
awk '{
data[$1,$3] = $2
} END {
for (k in data) {
print k, data[k]
}
}' test.txt
六、运算符
6.1 算术运算符
# 基本运算
awk 'BEGIN {
print 10 + 5 # 加: 15
print 10 - 5 # 减: 5
print 10 * 5 # 乘: 50
print 10 / 5 # 除: 2
print 10 % 3 # 取余: 1
print 2 ** 3 # 幂: 8
}'
# 自增/自减
awk 'BEGIN {
x = 10
print x++ # 10 (后增)
print x # 11
print ++x # 12 (前增)
}'
# 复合赋值
awk 'BEGIN {
x = 10
x += 5 # x = x + 5
x -= 3 # x = x - 3
x *= 2 # x = x * 2
x /= 4 # x = x / 4
print x
}'
6.2 字符串运算符
# 字符串拼接(空格)
awk 'BEGIN {
a = "Hello"
b = "World"
print a b # HelloWorld
print a " " b # Hello World
}'
# 字段拼接
awk '{print $1 "-" $2}' test.txt
6.3 比较运算符
# 比较
awk 'BEGIN {
print (10 == 10) # 1 (true)
print (10 != 5) # 1
print (10 > 5) # 1
print (10 < 5) # 0 (false)
print (10 >= 10) # 1
print (10 <= 5) # 0
}'
# 字符串比较
awk '$1 == "张三" {print}' test.txt
# 正则匹配
awk '$1 ~ /^张/ {print}' test.txt # 匹配
awk '$1 !~ /^张/ {print}' test.txt # 不匹配
6.4 逻辑运算符
# 逻辑运算
awk 'BEGIN {
print (1 && 1) # 1 (AND)
print (1 && 0) # 0
print (1 || 0) # 1 (OR)
print (0 || 0) # 0
print !0 # 1 (NOT)
print !1 # 0
}'
# 实际使用
awk '$2 > 20 && $2 < 30 {print}' test.txt
awk '$1 == "张三" || $1 == "李四" {print}' test.txt
awk '!($2 < 25) {print}' test.txt # 年龄不小于 25
6.5 三元运算符
# condition ? true_expr : false_expr
awk '{print $1, ($2 > 25 ? "年长" : "年轻")}' test.txt
# 输出:
# 张三 年轻
# 李四 年长
# 王五 年长
# 嵌套
awk '{
status = ($2 < 25 ? "年轻" : ($2 < 30 ? "适中" : "年长"))
print $1, status
}' test.txt
七、流程控制
7.1 条件语句
if-else
# 基本 if
awk '{if ($2 > 25) print $1, "年长"}' test.txt
# if-else
awk '{
if ($2 > 25) {
print $1, "年长"
} else {
print $1, "年轻"
}
}' test.txt
# if-else if-else
awk '{
if ($2 < 25) {
level = "年轻"
} else if ($2 < 30) {
level = "适中"
} else {
level = "年长"
}
print $1, level
}' test.txt
条件表达式
# 单行模式
awk '$2 > 25 {print $1, "年长"}' test.txt
# 范围模式
awk '/张三/,/王五/ {print}' test.txt
7.2 循环语句
for 循环
# 标准 for
awk 'BEGIN {
for (i = 1; i <= 5; i++) {
print i
}
}'
# 遍历数组
awk '{
count[$1]++
} END {
for (name in count) {
print name, count[name]
}
}' test.txt
# 遍历字段
awk '{
for (i = 1; i <= NF; i++) {
print "字段", i, ":", $i
}
}' test.txt
while 循环
# 基本 while
awk 'BEGIN {
i = 1
while (i <= 5) {
print i
i++
}
}'
# do-while
awk 'BEGIN {
i = 1
do {
print i
i++
} while (i <= 5)
}'
循环控制
# break - 跳出循环
awk 'BEGIN {
for (i = 1; i <= 10; i++) {
if (i == 5) break
print i
}
}'
# 输出: 1 2 3 4
# continue - 跳过当前迭代
awk 'BEGIN {
for (i = 1; i <= 5; i++) {
if (i == 3) continue
print i
}
}'
# 输出: 1 2 4 5
# next - 处理下一行
awk '{
if ($2 < 25) next
print $0
}' test.txt
# exit - 退出程序
awk '{
if (NR > 5) exit
print
}' test.txt
7.3 switch 语句(gawk)
# switch-case(GNU awk)
awk '{
switch ($3) {
case "北京":
print $1, "北方"
break
case "上海":
print $1, "华东"
break
case "广州":
print $1, "南方"
break
default:
print $1, "其他"
}
}' test.txt
八、函数
8.1 内置函数
数学函数
# 基本数学函数
awk 'BEGIN {
print int(3.7) # 取整: 3
print sqrt(16) # 平方根: 4
print exp(1) # e^x: 2.71828
print log(10) # 自然对数: 2.30259
print sin(0) # 正弦: 0
print cos(0) # 余弦: 1
print atan2(1,1) # 反正切: 0.785398
print rand() # 随机数 [0,1)
}'
# 随机数种子
awk 'BEGIN {
srand() # 使用时间作为种子
for (i = 1; i <= 5; i++) {
print int(rand() * 100) # 0-99 随机数
}
}'
# 实际应用
awk '{
sum += $2
sq_sum += $2 * $2
count++
} END {
mean = sum / count
variance = sq_sum / count - mean * mean
print "平均值:", mean
print "方差:", variance
print "标准差:", sqrt(variance)
}' test.txt
字符串函数
# 长度
awk '{print length($0)}' test.txt
awk '{print length($1)}' test.txt
# 子串 substr(s, start, len)
awk '{print substr($1, 1, 2)}' test.txt # 取前两个字
# 查找
awk 'BEGIN {
str = "hello world"
print index(str, "world") # 返回位置: 7
print index(str, "xyz") # 未找到: 0
}'
# 替换 sub/gsub
awk '{sub(/三/, "三丰"); print}' test.txt # 替换第一个
awk '{gsub(/[0-9]/, "X"); print}' test.txt # 替换所有
# 大小写
awk 'BEGIN {
print toupper("hello") # HELLO
print tolower("HELLO") # hello
}'
# 分割 split
awk '{
n = split($0, arr, " ")
for (i = 1; i <= n; i++) {
print i, arr[i]
}
}' test.txt
# 匹配 match
awk '{
if (match($0, /[0-9]+/)) {
print "数字位置:", RSTART
print "数字长度:", RLENGTH
print "匹配内容:", substr($0, RSTART, RLENGTH)
}
}' test.txt
# 格式化 sprintf
awk '{printf "%s\n", sprintf("%-10s %5d", $1, $2)}' test.txt
时间函数
# 时间函数
awk 'BEGIN {
print systime() # 当前时间戳
print mktime("2024 1 1 0 0 0") # 转换为时间戳
# 格式化时间
print strftime("%Y-%m-%d %H:%M:%S")
print strftime("%Y-%m-%d", systime())
}'
# 日期格式
awk 'BEGIN {
ts = systime()
print strftime("日期: %Y-%m-%d", ts)
print strftime("时间: %H:%M:%S", ts)
print strftime("星期: %A", ts)
print strftime("月份: %B", ts)
}'
# 时间计算
awk 'BEGIN {
start = mktime("2024 1 1 0 0 0")
end = systime()
days = int((end - start) / 86400)
print "距离 2024-01-01 已过", days, "天"
}'
类型函数
# isarray - 检查是否为数组
awk 'BEGIN {
arr[1] = 10
x = 5
print isarray(arr) # 1
print isarray(x) # 0
}'
# typeof - 获取类型
awk 'BEGIN {
x = 10
y = "hello"
arr[1] = 1
print typeof(x) # "number"
print typeof(y) # "string"
print typeof(arr) # "array"
}'
位运算函数
# 位运算
awk 'BEGIN {
print and(10, 6) # 按位与: 2
print or(10, 6) # 按位或: 14
print xor(10, 6) # 按位异或: 12
print compl(10) # 按位取反
print lshift(1, 3) # 左移: 8
print rshift(8, 3) # 右移: 1
}'
8.2 自定义函数
基本语法
function 函数名(参数1, 参数2, ...) {
# 函数体
return 返回值 # 可选
}
示例
# 简单函数
awk '
function square(x) {
return x * x
}
BEGIN {
print square(5) # 25
}'
# 带多个参数
awk '
function max(a, b) {
return (a > b) ? a : b
}
{
print max($2, 25)
}' test.txt
# 递归函数
awk '
function factorial(n) {
if (n <= 1) return 1
return n * factorial(n - 1)
}
BEGIN {
for (i = 1; i <= 10; i++) {
print i "! =", factorial(i)
}
}'
# 处理字符串
awk '
function trim(s) {
gsub(/^[ \t]+|[ \t]+$/, "", s)
return s
}
{
print trim($0)
}' test.txt
# 多返回值(通过数组)
awk '
function minmax(arr, result) {
result["min"] = arr[1]
result["max"] = arr[1]
for (i in arr) {
if (arr[i] < result["min"]) result["min"] = arr[i]
if (arr[i] > result["max"]) result["max"] = arr[i]
}
}
BEGIN {
a[1] = 5; a[2] = 2; a[3] = 8; a[4] = 1
minmax(a, r)
print "最小:", r["min"], "最大:", r["max"]
}'
局部变量
# 函数参数后面的变量是局部变量
awk '
function sum_squares(arr, i, total) {
total = 0
for (i in arr) {
total += arr[i] * arr[i]
}
return total
}
BEGIN {
a[1] = 1; a[2] = 2; a[3] = 3
print sum_squares(a) # 14
}'
九、高级特性
9.1 多文件处理
# 处理多个文件
awk '{print FILENAME, NR, FNR, $0}' file1.txt file2.txt
# FILENAME: 当前文件名
# NR: 全局行号
# FNR: 当前文件行号
# 合并两个文件
awk 'NR==FNR {a[$1]=$2; next} {print $0, a[$1]}' file1.txt file2.txt
# 比较两个文件
awk 'NR==FNR {a[$0]; next} !($0 in a)' file1.txt file2.txt
# 输出 file2 中有但 file1 中没有的行
9.2 多行记录
# 使用 RS 改变记录分隔符
# 处理以空行分隔的段落
awk 'BEGIN {RS=""; FS="\n"} {print "段落", NR, "行数", NF}' data.txt
# 处理特定格式的多行记录
awk '
BEGIN {RS=""; FS="\n"}
{
for (i = 1; i <= NF; i++) {
split($i, field, ": ")
data[field[1]] = field[2]
}
print data["Name"], data["Email"]
}' data.txt
9.3 输出重定向
# 重定向到文件
awk '{print > "output.txt"}' input.txt
# 追加到文件
awk '{print >> "output.txt"}' input.txt
# 管道输出
awk '{print | "sort"}' input.txt
# 条件输出到不同文件
awk '
{
if ($3 == "北京") print > "beijing.txt"
else if ($3 == "上海") print > "shanghai.txt"
else print > "other.txt"
}' test.txt
# 使用 tee 输出到多个目标
awk '{print | "tee output.txt"}' input.txt
9.4 读取外部命令
# 读取命令输出
awk 'BEGIN {
while (("date" | getline line) > 0) {
print line
}
close("date")
}'
# 读取文件
awk '
BEGIN {
while ((getline line < "config.txt") > 0) {
print line
}
close("config.txt")
}'
# 写入命令
awk '{
print $0 | "mail -s test user@example.com"
}' input.txt
9.5 脚本文件
创建 awk 脚本
#!/usr/bin/awk -f
# report.awk - 生成报表
BEGIN {
FS = ","
OFS = "\t"
print "姓名", "年龄", "城市"
print "----", "----", "----"
}
{
# 跳过注释行
if ($0 ~ /^#/) next
# 数据处理
total += $2
count++
# 输出
print $1, $2, $3
}
END {
print "----", "----", "----"
print "平均年龄:", total/count
}
使用脚本
# 添加执行权限
chmod +x report.awk
# 直接执行
./report.awk data.csv
# 或使用 awk -f
awk -f report.awk data.csv
9.6 与 Shell 结合
# 使用 Shell 变量
name="张三"
awk -v user="$name" '$1 == user {print}' test.txt
# 使用命令替换
awk -v date="$(date +%Y-%m-%d)" '{print date, $0}' test.txt
# 在 awk 中执行 Shell 命令
awk '{system("echo " $1)}' test.txt
# 使用管道
cat test.txt | awk '{print $1}'
ps aux | awk '$3 > 1.0 {print}'
9.7 调试技巧
# 使用 print 调试
awk '{print "DEBUG: NR=" NR ", $1=" $1 > "/dev/stderr"} {print}' test.txt
# 使用 dbmalloc(如果编译时启用)
# gawk --debug script.awk
# 分步执行
awk '{print NR, NF, $0}' test.txt | head
# 检查变量
awk '{a[$1]=$2} END {for(k in a) print k, a[k]}' test.txt
十、实战示例
10.1 文本处理
统计信息
# 统计行数
awk 'END {print NR}' file.txt
# 统计单词数
awk '{total += NF} END {print total}' file.txt
# 统计字符数
awk '{total += length($0) + 1} END {print total}' file.txt
# 统计文件大小(字节)
awk '{total += length($0) + 1} END {print total, "bytes"}' file.txt
# 统计每个单词出现次数
awk '{for(i=1;i<=NF;i++) count[$i]++} END {for(w in count) print count[w], w}' file.txt | sort -rn | head
数据清洗
# 删除空行
awk 'NF' file.txt
# 删除重复行
awk '!seen[$0]++' file.txt
# 删除连续重复行
awk 'a[$0]++==0' file.txt
# 删除特定列
awk '{$2=""; print $0}' file.txt # 删除第二列
awk '{$1=$2=$3=""; $1=$1; print}' file.txt # 删除前三列
# 合并连续行为一行
awk '{printf "%s%s", $0, (NR%3==0?"\n":" ")}' file.txt # 每 3 行合并
格式转换
# CSV 转 TSV
awk 'BEGIN{FS=","; OFS="\t"} {$1=$1; print}' file.csv
# 固定宽度转 CSV
awk 'BEGIN{FIELDWIDTHS="10 20 10"} {print $1","$2","$3}' file.txt
# JSON(简单版)
awk '
BEGIN {print "["}
{
gsub(/"/, "\\\"", $0)
printf " {\"line\": %d, \"content\": \"%s\"},\n", NR, $0
}
END {print "]"}
' file.txt
10.2 数据分析
基本统计
# 求和
awk '{sum += $1} END {print sum}' data.txt
# 平均值
awk '{sum += $1} END {print sum/NR}' data.txt
# 最大值/最小值
awk 'NR==1 {min=$1; max=$1} {if($1<min) min=$1; if($1>max) max=$1} END {print "Min:", min, "Max:", max}' data.txt
# 标准差
awk '
{
sum += $1
sumsq += $1*$1
count++
}
END {
mean = sum/count
variance = sumsq/count - mean*mean
stddev = sqrt(variance)
print "Mean:", mean
print "StdDev:", stddev
}' data.txt
# 中位数
awk '
{a[NR]=$1}
END {
n = asort(a)
if (n%2==1) print a[int(n/2)+1]
else print (a[n/2]+a[n/2+1])/2
}' data.txt
分组统计
# 按第一列分组计数
awk '{count[$1]++} END {for(k in count) print k, count[k]}' data.txt
# 按第一列分组求和
awk '{sum[$1]+=$2} END {for(k in sum) print k, sum[k]}' data.txt
# 按多列分组
awk '{key=$1","$2; count[key]++} END {for(k in count) print k, count[k]}' data.txt
# 计算每组的平均值
awk '
{
sum[$1] += $2
count[$1]++
}
END {
for (k in sum) {
print k, sum[k]/count[k]
}
}' data.txt
数据透视
# 简单交叉表
awk '
{
row[$1]++
col[$2]++
data[$1,$2]++
}
END {
# 打印表头
printf "%-10s", ""
for (c in col) printf "%10s", c
print ""
# 打印数据
for (r in row) {
printf "%-10s", r
for (c in col) {
printf "%10d", data[r,c]+0
}
print ""
}
}' data.txt
10.3 日志分析
Apache/Nginx 日志
# 提取 IP 地址
awk '{print $1}' access.log
# 统计每个 IP 访问次数
awk '{count[$1]++} END {for(ip in count) print count[ip], ip}' access.log | sort -rn | head
# 统计状态码
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# 提取特定时间段的日志
awk '/01\/Jan\/2024:10:00/,/01\/Jan\/2024:11:00/' access.log
# 统计每个 URL 访问次数
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head
# 找出响应时间最慢的 10 个请求
awk '{print $NF, $7}' access.log | sort -rn | head -10
# 统计每小时的请求数
awk '{print substr($4, 14, 2)}' access.log | sort | uniq -c
# 找出访问量最大的前 10 个 IP
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 统计 User-Agent
awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -rn | head
应用日志
# 统计各个日志级别的数量
awk '/ERROR/{error++} /WARN/{warn++} /INFO/{info++} END{print "ERROR:",error,"WARN:",warn,"INFO:",info}' app.log
# 提取错误日志
awk '/ERROR/' app.log
# 提取特定时间范围的日志
awk '/2024-01-01 10:00:00/,/2024-01-01 11:00:00/' app.log
# 统计每个小时错误数
awk '/ERROR/ {print substr($1, 1, 13)}' app.log | uniq -c
# 提取异常堆栈
awk '/Exception/,/^[[:space:]]*$/' app.log
10.4 系统管理
进程管理
# 查找占用 CPU 最高的进程
ps aux | awk 'NR>1 {print $3, $11}' | sort -rn | head
# 查找占用内存最高的进程
ps aux | awk 'NR>1 {print $4, $11}' | sort -rn | head
# 查找特定用户的进程
ps aux | awk '$1=="username"'
# 统计每个用户的进程数
ps aux | awk '{count[$1]++} END {for(u in count) print u, count[u]}'
# 杀死占用内存过多的进程
ps aux | awk '$4 > 50 {print $2}' | xargs kill -9
文件系统
# 找出最大的 10 个文件
ls -l | awk '{print $5, $9}' | sort -rn | head
# 统计目录大小
du -sk * | awk '{sum+=$1} END {print sum"KB"}'
# 查找空文件
ls -l | awk '$5==0 {print $9}'
# 按扩展名统计文件数量
ls -l | awk -F. '{print $NF}' | sort | uniq -c | sort -rn
# 分析 /etc/passwd
awk -F: '{print $1, $3, $7}' /etc/passwd
awk -F: '$3>=1000 {print $1}' /etc/passwd # 普通用户
网络分析
# 统计连接状态
netstat -an | awk '/^tcp/ {print $6}' | sort | uniq -c
# 统计每个 IP 的连接数
netstat -an | awk '/^tcp/ {print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn
# 查找监听端口
netstat -tlnp | awk '{print $4, $7}'
# 分析访问日志中的国家(需要 IP 数据库)
awk '{print $1}' access.log | sort -u | while read ip; do
echo "$ip $(geoiplookup $ip)"
done
10.5 数据处理
CSV 处理
# 提取列
awk -F, '{print $1, $3}' data.csv
# 过滤行
awk -F, '$3 > 100 {print}' data.csv
# 计算列总和
awk -F, '{sum+=$3} END {print sum}' data.csv
# 添加行号
awk -F, '{print NR, $0}' data.csv
# 添加表头
awk -F, 'BEGIN{print "ID,Name,Value"} {print}' data.csv
# 合并两个 CSV
awk -F, 'NR==FNR{a[$1]=$2;next} {print $0,a[$1]}' file2.csv file1.csv
# CSV 转 Markdown 表格
awk -F, '
NR==1 {print "| " $0 " |"; print "|---|---|---|"; next}
{print "| " $0 " |"}
' data.csv
数据转换
# JSON 转 CSV(简单版)
awk '
BEGIN {FS=":"; in_obj=0}
/{/ {in_obj=1; next}
/}/ {in_obj=0; if(line) print line; line=""; next}
in_obj && /"[^"]+":/ {
gsub(/[",]/, "", $2)
line = (line ? line "," : "") $2
}
' data.json
# 矩阵转置
awk '
{
for (i=1; i<=NF; i++) {
a[NR,i] = $i
}
}
NF > max_nf {max_nf = NF}
END {
for (i=1; i<=max_nf; i++) {
for (j=1; j<=NR; j++) {
printf "%s%s", a[j,i], (j==NR?"\n":" ")
}
}
}' matrix.txt
10.6 报表生成
简单报表
awk '
BEGIN {
FS = ","
printf "%-20s %10s %10s\n", "Name", "Sales", "Commission"
printf "%-20s %10s %10s\n", "----", "-----", "----------"
}
{
sales = $2
commission = sales * 0.1
total_sales += sales
total_commission += commission
printf "%-20s %10.2f %10.2f\n", $1, sales, commission
}
END {
printf "%-20s %10s %10s\n", "----", "-----", "----------"
printf "%-20s %10.2f %10.2f\n", "TOTAL", total_sales, total_commission
}' sales.csv
分组报表
awk '
BEGIN {
FS = ","
OFS = "\t"
print "部门", "人数", "总工资", "平均工资"
print "----", "----", "------", "--------"
}
{
dept = $2
salary = $3
count[dept]++
total[dept] += salary
}
END {
for (d in count) {
printf "%-10s %5d %10.2f %10.2f\n", d, count[d], total[d], total[d]/count[d]
}
}' employees.csv
十一、技巧与最佳实践
11.1 性能优化
# 1. 使用 next 跳过不必要的处理
awk '/pattern/{print; next} {其他处理}' file.txt
# 2. 提前退出
awk '/pattern/{print; exit}' file.txt
# 3. 使用字段而不是字符串处理
# 快
awk '{print $1}' file.txt
# 慢
awk '{split($0,a," "); print a[1]}' file.txt
# 4. 避免重复计算
# 好
awk '{v=$2*$3; print v, v/100}' file.txt
# 差
awk '{print $2*$3, $2*$3/100}' file.txt
# 5. 使用内置函数
# 好
awk '{print length($0)}' file.txt
# 差
awk '{n=0; while(substr($0,n+1,1)!="") n++; print n}' file.txt
11.2 调试技巧
# 1. 使用 print 调试
awk '{print "DEBUG: " NR " " $0 > "/dev/stderr"; 实际代码}' file.txt
# 2. 检查变量
awk '{a[$1]=$2} END {for(k in a) print k, a[k] > "/dev/stderr"}' file.txt
# 3. 分步测试
awk 'NR<=5' file.txt # 只处理前 5 行
# 4. 使用 lint 检查
awk --lint '{print $1}' file.txt
# 5. 查看解析结果
awk -d '{print $1}' file.txt
11.3 常见陷阱
陷阱 1:数字与字符串比较
# 错误:字符串比较
awk '$1 > "10"' file.txt # "9" > "10" 会返回 true
# 正确:数字比较
awk '$1 > 10' file.txt # 强制数字比较
# 或使用 +0
awk '$1+0 > 10' file.txt
陷阱 2:字段不存在
# 检查字段是否存在
awk 'NF >= 3 {print $3}' file.txt
# 使用默认值
awk '{print ($3 ? $3 : "N/A")}' file.txt
陷阱 3:空行处理
# 跳过空行
awk 'NF' file.txt
awk '/./' file.txt
awk 'length' file.txt
陷阱 4:FS 设置时机
# 错误:FS 在处理时设置已经晚了
awk '{FS=","} {print $1}' file.csv
# 正确:在 BEGIN 中设置
awk 'BEGIN{FS=","} {print $1}' file.csv
# 或使用 -F
awk -F, '{print $1}' file.csv
陷阱 5:数组遍历顺序
# 数组遍历顺序不确定
awk '{a[NR]=$0} END {for(k in a) print k, a[k]}' file.txt
# 如果需要顺序,使用索引
awk '{a[NR]=$0} END {for(i=1;i<=NR;i++) print i, a[i]}' file.txt
11.4 常用脚本
# 计算文件 MD5(配合 md5sum)
awk '{cmd="echo \""$0"\" | md5sum"; cmd|getline md5; close(cmd); print md5, $0}' file.txt
# 行号补零
awk '{printf "%05d %s\n", NR, $0}' file.txt
# 随机排序
awk 'BEGIN{srand()} {print rand()"\t"$0}' file.txt | sort -n | cut -f2-
# 去重并保持顺序
awk '!seen[$0]++' file.txt
# 合并相邻相同行
awk '{if($0!=prev){print; prev=$0}}' file.txt
# 删除 C/C++ 注释
awk '{gsub(/\/\/.*/,""); gsub(/\/\*.*\*\//,""); print}' file.c
# URL 解码
awk -v RS='%..' '{printf "%s", RT=="\n"?RT:sprintf("%c",("0x"substr(RT,2))+0)}' file.txt
十二、awk vs 其他工具
12.1 awk vs sed
# sed:简单替换
sed 's/old/new/' file.txt
# awk:更灵活的处理
awk '{gsub(/old/, "new"); print}' file.txt
# sed 适合:简单文本替换、删除
# awk 适合:按列处理、复杂计算
12.2 awk vs grep
# grep:查找匹配行
grep "pattern" file.txt
# awk:查找并处理
awk '/pattern/ {print $1, $3}' file.txt
# grep 适合:简单搜索
# awk 适合:搜索+处理
12.3 awk vs cut
# cut:简单列提取
cut -d',' -f1,3 file.txt
# awk:更强大的列处理
awk -F, '{print $1, $3*2}' file.txt
# cut 适合:简单固定位置提取
# awk 适合:复杂字段处理
12.4 awk vs sort
# sort:排序
sort -t',' -k2 -n file.txt
# awk:排序+处理
awk -F, '{print $2, $0}' file.txt | sort -n | awk '{print $2}'
# sort 适合:简单排序
# awk 适合:排序前后的数据处理
12.5 awk vs Python
# Python:更强大但更复杂
python3 -c "
import sys
for line in sys.stdin:
fields = line.strip().split()
print(fields[0], int(fields[1])*2)
"
# awk:简洁高效
awk '{print $1, $2*2}'
# awk 适合:简单文本处理
# Python 适合:复杂逻辑处理