awk 命令全面详解
一、awk 基本概念
1. 什么是 awk?
- 名称来源 :三位创始人 Alfred A ho、Peter W einberger、Brian Kernighan
- 本质 :一种文本处理编程语言,也是命令行工具
- 特点:逐行处理文本,基于模式匹配和处理
2. awk 工作原理
# awk 工作流程:
# 1. 读取输入流的一行
# 2. 按字段分隔符分割成多个字段
# 3. 执行匹配的模式和对应的动作
# 4. 输出结果
3. 基本语法
awk 'pattern { action }' input_file
awk -F separator 'pattern { action }' input_file
二、基础用法示例
1. 打印整行
# 打印文件所有行
awk '{ print }' file.txt
awk '{ print $0 }' file.txt # 等价
# 打印第一行(NR是行号变量)
awk 'NR == 1 { print }' file.txt
2. 打印特定字段
# 默认以空格或制表符分割
awk '{ print $1 }' file.txt # 打印第1列
awk '{ print $1, $3 }' file.txt # 打印第1和第3列
awk '{ print $NF }' file.txt # 打印最后一列(NF是字段数)
awk '{ print $(NF-1) }' file.txt # 打印倒数第二列
3. 基本示例
# 示例数据:students.txt
# Name Age Score Grade
# Alice 20 85 A
# Bob 22 92 A+
# Carol 21 78 B
# 打印所有学生的名字和分数
awk '{ print $1, $3 }' students.txt
# 打印分数大于80的学生
awk '$3 > 80 { print $1, $3 }' students.txt
# 计算平均年龄
awk '{ sum += $2 } END { print "平均年龄:", sum/NR }' students.txt
三、awk 程序结构
1. BEGIN 和 END 块
# BEGIN:处理前执行,用于初始化
# END:处理后执行,用于总结
awk 'BEGIN { print "开始处理..." }
{ print $0 }
END { print "处理完成,共", NR, "行" }' file.txt
2. 完整的 awk 程序结构
awk '
BEGIN {
# 初始化代码
FS = "," # 设置字段分隔符
OFS = "\t" # 设置输出分隔符
count = 0
}
# 模式-动作对
/pattern/ { action } # 正则匹配
$1 > 100 { action } # 字段条件
NR == 1 { action } # 行号条件
{ action } # 无条件,每行执行
END {
# 结束处理
print "总计:", count
}
' input_file
四、内置变量详解
1. 字段和行变量
# FS:输入字段分隔符(默认空格/制表符)
awk -F "," '{ print $1 }' file.csv
awk 'BEGIN { FS = ":" } { print $1 }' /etc/passwd
# OFS:输出字段分隔符(默认空格)
awk 'BEGIN { OFS = " -> " } { print $1, $2 }' file.txt
# RS:记录分隔符(默认换行符)
awk 'BEGIN { RS = "" } { print }' file.txt # 以空行分隔段落
# ORS:输出记录分隔符(默认换行符)
awk 'BEGIN { ORS = "\n\n" } { print }' file.txt
# NF:当前行的字段数量
awk '{ print "行", NR, "有", NF, "个字段" }' file.txt
awk '{ if (NF > 5) print "长行:", $0 }' file.txt
# NR:已读的记录数(行号)
awk 'NR <= 10 { print }' file.txt # 前10行
awk 'NR % 2 == 0 { print }' file.txt # 偶数行
# FNR:当前文件的记录数(多文件时有用)
awk '{ print FILENAME, FNR, $0 }' file1.txt file2.txt
2. 其他内置变量
# FILENAME:当前文件名
awk '{ print FILENAME, $0 }' *.txt
# ARGC:命令行参数数量
# ARGV:命令行参数数组
awk 'BEGIN {
print "参数个数:", ARGC;
for(i=0; i<ARGC; i++)
print "ARGV[" i "] =", ARGV[i]
}' file1 file2
# ENVIRON:环境变量数组
awk 'BEGIN { print "用户:", ENVIRON["USER"] }'
# CONVFMT:数字转换格式(默认"%.6g")
awk 'BEGIN { CONVFMT = "%.2f"; print 1/3 }' # 0.33
五、字段操作
1. 字段引用和修改
# 修改字段值
awk '{ $1 = "前缀_" $1; print }' file.txt
# 添加新字段
awk '{ $(NF+1) = "新字段"; print }' file.txt
# 删除字段
awk '{ $2 = ""; print }' file.txt # 置空
awk '{ for(i=2; i<=NF; i++) $i = $(i+1); NF--; print }' file.txt # 删除第二列
# 重新排列字段
awk '{ print $3, $1, $2 }' file.txt
2. 字段分割控制
# 使用多个分隔符
awk -F "[ :,]+" '{ print $1, $2 }' file.txt # 空格、冒号、逗号
# 固定宽度字段(不常用)
awk 'BEGIN { FIELDWIDTHS = "3 5 2" } { print $1, $2, $3 }' file.txt
六、模式匹配
1. 正则表达式模式
# 匹配包含 "error" 的行
awk '/error/ { print }' file.txt
# 匹配以 "start" 开头的行
awk '/^start/ { print }' file.txt
# 匹配以 ".log" 结尾的行
awk '/\.log$/ { print }' file.txt
# 不匹配模式(取反)
awk '!/error/ { print }' file.txt
# 组合匹配
awk '/error/ && /critical/ { print }' file.txt
2. 字段匹配
# 第一列等于特定值
awk '$1 == "Alice" { print }' file.txt
# 第二列大于100
awk '$2 > 100 { print }' file.txt
# 最后一列匹配正则
awk '$NF ~ /[0-9]+/ { print }' file.txt
# 不匹配
awk '$1 !~ /^#/ { print }' file.txt # 不以#开头
3. 行号模式
# 特定行号
awk 'NR == 5 { print }' file.txt
# 行号范围
awk 'NR >= 10 && NR <= 20 { print }' file.txt
# 每隔n行
awk 'NR % 5 == 0 { print }' file.txt
4. 复合模式
# 逻辑与
awk '$1 == "admin" && $3 > 90 { print }' file.txt
# 逻辑或
awk '$1 == "Alice" || $1 == "Bob" { print }' file.txt
# 逻辑非
awk '!($1 == "test") { print }' file.txt
# 括号分组
awk '($1 == "A" && $2 > 10) || ($1 == "B" && $2 < 5) { print }'
七、awk 运算符
1. 算术运算符
awk '{ print $1 + $2 }' # 加
awk '{ print $1 - $2 }' # 减
awk '{ print $1 * $2 }' # 乘
awk '{ print $1 / $2 }' # 除
awk '{ print $1 % $2 }' # 取模
awk '{ print $1 ^ $2 }' # 幂运算
awk '{ print -$1 }' # 负号
2. 赋值运算符
# 基本赋值
awk '{ sum = $1; print sum }'
# 复合赋值
awk '{ sum += $1 } END { print sum }' # 累加
awk '{ count++ } END { print count }' # 计数
awk '{ max = ($1 > max) ? $1 : max } END { print max }' # 最大值
3. 比较运算符
awk '$1 == $2' # 等于
awk '$1 != $2' # 不等于
awk '$1 > $2' # 大于
awk '$1 >= $2' # 大于等于
awk '$1 < $2' # 小于
awk '$1 <= $2' # 小于等于
# 字符串比较
awk '$1 == "test"' # 字符串等于
awk '$1 ~ /regex/' # 匹配正则
awk '$1 !~ /regex/' # 不匹配正则
4. 三元运算符
awk '{ grade = ($2 >= 60) ? "及格" : "不及格"; print $1, grade }' scores.txt
awk '{ print $1, ($2 > $3 ? "增加" : "减少"), abs($2 - $3) }' data.txt
八、控制结构
1. if-else 语句
# 基本if
awk '{ if ($3 > 80) print $1, "优秀" }' scores.txt
# if-else
awk '{
if ($3 >= 90) grade = "A"
else if ($3 >= 80) grade = "B"
else if ($3 >= 70) grade = "C"
else grade = "D"
print $1, grade
}' scores.txt
2. 循环语句
# for 循环
awk '{
sum = 0
for(i=1; i<=NF; i++) {
sum += $i
}
print "总和:", sum, "平均:", sum/NF
}' numbers.txt
# while 循环
awk '{
i = 1
while (i <= NF) {
printf "%s ", $i^2
i++
}
print ""
}' numbers.txt
# do-while 循环
awk '{
i = 1
do {
printf "%s ", $i
i++
} while (i <= NF)
print ""
}' file.txt
3. 数组操作
# 关联数组(类似字典/哈希表)
awk '{ count[$1]++ } END { for(user in count) print user, count[user] }' log.txt
# 多维数组(模拟)
awk '{ data[$1,$2] = $3 } END { print data["a","b"] }' file.txt
# 数组排序
awk '{ arr[$1] += $2 }
END {
n = asorti(arr, sorted) # 按键排序
for(i=1; i<=n; i++)
print sorted[i], arr[sorted[i]]
}' data.txt
# 删除数组元素
awk '{ delete arr[$1] }' # 删除键为$1的元素
九、内置函数
1. 数学函数
awk '{ print sin($1) }' # 正弦
awk '{ print cos($1) }' # 余弦
awk '{ print sqrt($1) }' # 平方根
awk '{ print log($1) }' # 自然对数
awk '{ print exp($1) }' # 指数
awk '{ print int($1) }' # 取整
awk '{ print rand() }' # 随机数 [0,1)
awk 'BEGIN { srand(); print int(rand()*100) }' # 随机整数
2. 字符串函数
# 长度
awk '{ print length($1) }' # 字符串长度
# 子字符串
awk '{ print substr($1, 2, 3) }' # 从位置2开始取3个字符
# 查找
awk '{ print index($1, "abc") }' # 返回位置,未找到返回0
# 匹配
awk '{ print match($1, /[0-9]+/) }' # 返回匹配位置
awk '{ print RSTART, RLENGTH }' # 匹配信息
# 替换
awk '{ gsub(/old/, "new", $1); print }' # 全局替换
awk '{ sub(/old/, "new", $1); print }' # 只替换第一个
# 分割
awk 'BEGIN { split("a,b,c,d", arr, ","); for(i in arr) print arr[i] }'
# 格式化
awk '{ printf "%-10s %5d\n", $1, $2 }' # 格式化输出
# 大小写转换
awk '{ print toupper($1) }' # 转大写
awk '{ print tolower($1) }' # 转小写
3. 时间函数
# 获取时间戳
awk 'BEGIN { print systime() }' # 当前时间戳
# 格式化时间
awk 'BEGIN {
print strftime("%Y-%m-%d %H:%M:%S") # 当前时间
print strftime("%Y-%m-%d", systime()-86400) # 昨天
}'
十、高级特性
1. 用户自定义函数
# 定义和使用函数
awk '
function max(a, b) {
return a > b ? a : b
}
function min(a, b) {
return a < b ? a : b
}
{
print "最大值:", max($1, $2)
print "最小值:", min($1, $2)
}' numbers.txt
# 带默认参数的函数
awk '
function greet(name, greeting) {
if (greeting == "") greeting = "Hello"
return greeting ", " name
}
{
print greet($1)
print greet($1, "Hi")
}' names.txt
2. 包含外部文件
# functions.awk
function square(x) { return x*x }
function cube(x) { return x*x*x }
# 主脚本
awk -f functions.awk -e '
{
print "平方:", square($1)
print "立方:", cube($1)
}' numbers.txt
3. 多行记录处理
# 处理多行记录(以空行分隔)
awk 'BEGIN { RS = ""; FS = "\n" }
{
print "记录", NR, "有", NF, "行"
for(i=1; i<=NF; i++)
print " 行", i, ":", $i
}' multi_line.txt
十一、实际应用场景
1. 日志分析
# 分析 Nginx 访问日志
# 统计每个IP的访问次数
awk '{ ip[$1]++ }
END {
for(i in ip)
print ip[i], i
}' access.log | sort -nr
# 统计状态码
awk '{ status[int($9/100)*100]++ }
END {
for(s in status)
print s, "xx:", status[s]
}' access.log
# 计算平均响应时间
awk 'NF > 0 {
total += $(NF-1); count++
}
END {
if(count > 0)
print "平均响应时间:", total/count, "ms"
}' access.log
2. 系统监控
# 分析进程内存使用
ps aux | awk '
NR > 1 {
mem[$11] += $6 # 按进程名累加内存
}
END {
for(p in mem)
printf "%-30s %10d KB\n", p, mem[p]
}' | sort -k2 -nr
# 监控磁盘使用
df -h | awk '
NR > 1 {
use = $5;
sub(/%/, "", use);
if(use > 80)
print "警告: "$1" 使用率 "$5
}'
3. 数据处理
# CSV 文件处理
awk -F ',' '
BEGIN { OFS = "," }
{
# 计算总分
total = $2 + $3 + $4
avg = total / 3
# 添加总分和平均分列
$(NF+1) = total
$(NF+1) = sprintf("%.2f", avg)
print
}' scores.csv
# JSON 简单解析(基础版)
echo '{"name":"Alice","age":20}' | awk -F '[:,]' '{
gsub(/[{}"]/, "", $0)
for(i=1; i<=NF; i+=2)
print $i " = " $(i+1)
}'
4. 文本处理
# 提取特定内容
awk '/^Title:/ { title = substr($0, 8) }
/^Author:/ { author = substr($0, 9) }
/^---$/ { print title " - " author; title = author = "" }' books.txt
# 文本格式化
awk '{
# 每行缩进4个空格
print " " $0
# 每两行添加空行
if(NR % 2 == 0) print ""
}' file.txt
# 单词统计(类似wc)
awk '{
chars += length($0) + 1 # +1 换行符
words += NF
lines++
}
END {
print " 行:", lines
print " 词:", words
print " 字符:", chars
}' file.txt
十二、awk 单行命令宝典
1. 文件处理
# 打印文件行数
awk 'END { print NR }' file.txt
# 打印第一列不重复的值
awk '!seen[$1]++ { print $1 }' file.txt
# 逆序打印行
awk '{ lines[NR] = $0 } END { for(i=NR; i>0; i--) print lines[i] }' file.txt
# 合并所有行
awk '{ printf "%s", $0 } END { print "" }' file.txt
# 打印最长行
awk 'length > max { max = length; line = $0 } END { print max, line }' file.txt
2. 数据计算
# 计算列总和
awk '{ sum += $1 } END { print sum }' numbers.txt
# 计算列平均值
awk '{ sum += $1 } END { if(NR>0) print sum/NR }' numbers.txt
# 计算列最大值
awk 'NR==1 { max=$1 } $1>max { max=$1 } END { print max }' numbers.txt
# 计算列最小值
awk 'NR==1 { min=$1 } $1<min { min=$1 } END { print min }' numbers.txt
# 计算标准差
awk '{ sum+=$1; sumsq+=$1*$1 } END { print sqrt(sumsq/NR - (sum/NR)**2) }' numbers.txt
3. 文本转换
# 制表符转空格
awk '{ gsub(/\t/, " "); print }' file.txt
# 空格转逗号(CSV)
awk '{ for(i=1; i<NF; i++) printf "%s,", $i; print $NF }' file.txt
# 转换为大写
awk '{ print toupper($0) }' file.txt
# 转换为小写
awk '{ print tolower($0) }' file.txt
4. 系统管理
# 查找重复文件(按大小)
find . -type f -exec ls -l {} \; | awk '{
size = $5
if(size in sizes)
print "重复大小:", size, "\n 旧:", sizes[size], "\n 新:", $9
else
sizes[size] = $9
}'
# 监控网络连接数
netstat -an | awk '/^tcp/ {
state[$NF]++
}
END {
for(s in state)
print s, state[s]
}' | sort -rn
十三、性能优化技巧
1. 减少字符串操作
# 差 - 多次字符串连接
awk '{ str = ""; for(i=1;i<=NF;i++) str = str $i ","; print str }'
# 好 - 使用数组和join
awk '{ for(i=1;i<=NF;i++) arr[i] = $i;
for(i=1;i<=NF;i++) printf "%s%s", arr[i], (i==NF?"\n":",") }'
2. 使用内置函数
# 差 - 自己实现
awk '{ if($1+0 == $1) print "数字" }' # 判断是否为数字
# 好 - 使用正则
awk '$1 ~ /^[0-9]+$/ { print "数字" }'
3. 避免不必要操作
# 差 - 每行都执行BEGIN操作
awk '{ FS=","; print $1 }' file.csv
# 好 - 在BEGIN中设置
awk 'BEGIN { FS="," } { print $1 }' file.csv
4. 大文件处理策略
# 使用next提前退出
awk '$1 != "important" { next } { print }' largefile.txt
# 处理部分数据
awk 'NR % 100 == 0 { print }' largefile.txt # 每100行处理一行
# 分块处理
awk -v chunk=100000 '
BEGIN { count=0 }
{
data[NR] = $0
if(NR % chunk == 0) {
# 处理一个块
for(i=1; i<=chunk; i++) print data[i]
delete data
}
}
END {
# 处理剩余数据
for(i in data) print data[i]
}' hugefile.txt
十四、awk vs grep vs sed 对比
| 工具 | 主要用途 | 优点 | 缺点 |
|---|---|---|---|
| grep | 文本搜索 | 简单快速,正则匹配 | 功能有限 |
| sed | 流编辑 | 文本替换,编辑 | 数据处理能力弱 |
| awk | 文本处理 | 编程语言,字段处理 | 语法较复杂 |
选择指南
# 只需要搜索 -> grep
grep "pattern" file.txt
# 只需要替换 -> sed
sed 's/old/new/g' file.txt
# 需要字段处理、计算 -> awk
awk '{ sum += $1 } END { print sum }' file.txt
# 复杂文本处理 -> awk 脚本
awk -f process.awk file.txt
十五、实战脚本示例
1. 日志分析监控脚本
#!/bin/bash
# log_analyzer.awk - 综合日志分析
BEGIN {
print "=== 日志分析报告 ==="
print "分析时间: " strftime("%Y-%m-%d %H:%M:%S")
print "======================================="
# 初始化统计
error_count = 0
warning_count = 0
info_count = 0
# 按小时统计
for(i=0; i<24; i++) hour_count[i] = 0
# IP统计
delete ip_count
}
# 分析每行日志
{
# 提取时间(假设格式: [2024-01-15 14:30:00])
if(match($0, /\[([0-9-]+) ([0-9:]+)\]/, time)) {
hour = substr(time[2], 1, 2) + 0 # 转换为数字
hour_count[hour]++
}
# 统计日志级别
if(/ERROR/) {
error_count++
print "[错误] " $0 > "errors.log"
}
else if(/WARN/) {
warning_count++
}
else if(/INFO/) {
info_count++
}
# 提取IP(简单匹配)
if(match($0, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/, ip)) {
ip_count[ip[0]]++
}
}
END {
print "\n=== 统计摘要 ==="
print "总行数: " NR
print "错误数: " error_count
print "警告数: " warning_count
print "信息数: " info_count
print "\n=== 按小时分布 ==="
for(h=0; h<24; h++) {
if(hour_count[h] > 0) {
printf "%02d时: %6d 行\n", h, hour_count[h]
}
}
print "\n=== TOP 10 IP ==="
# 找出最多的10个IP
n = asorti(ip_count, sorted, "@val_num_desc")
for(i=1; i<=10 && i<=n; i++) {
printf "%-15s %6d 次\n", sorted[i], ip_count[sorted[i]]
}
# 错误率
if(NR > 0) {
error_rate = error_count / NR * 100
printf "\n错误率: %.2f%%\n", error_rate
}
}
2. 学生成绩管理系统
#!/bin/bash
# grade_system.awk - 学生成绩管理
BEGIN {
FS = ","
OFS = ","
print "学号,姓名,语文,数学,英语,总分,平均分,等级"
# 等级标准
grade_A = 90
grade_B = 80
grade_C = 70
grade_D = 60
}
NR > 1 { # 跳过标题行
# 计算总分和平均分
total = $3 + $4 + $5
average = total / 3
# 确定等级
if(average >= grade_A) grade = "A"
else if(average >= grade_B) grade = "B"
else if(average >= grade_C) grade = "C"
else if(average >= grade_D) grade = "D"
else grade = "F"
# 输出结果
print $1, $2, $3, $4, $5, total, sprintf("%.2f", average), grade
# 统计信息
scores[NR-1] = average
sum += average
if(average > max || NR==2) max = average
if(average < min || NR==2) min = average
# 科目统计
chinese_sum += $3
math_sum += $4
english_sum += $5
}
END {
print "\n=== 成绩分析报告 ==="
print "学生人数: " (NR-1)
if(NR > 1) {
print "平均分: " sprintf("%.2f", sum/(NR-1))
print "最高分: " sprintf("%.2f", max)
print "最低分: " sprintf("%.2f", min)
print "\n科目平均分:"
print " 语文: " sprintf("%.2f", chinese_sum/(NR-1))
print " 数学: " sprintf("%.2f", math_sum/(NR-1))
print " 英语: " sprintf("%.2f", english_sum/(NR-1))
}
}
十六、最佳实践总结
1. 代码组织
# 1. 将复杂脚本保存到文件
awk -f script.awk input.txt
# 2. 添加注释说明
awk '
# 这是一个学生成绩处理脚本
# 作者: Your Name
# 日期: 2024-01-15
BEGIN {
# 初始化代码
FS = ","
total = 0
}
# 主处理逻辑
{
# 计算总分
sum = $2 + $3 + $4
print $1, sum
}
END {
# 汇总输出
print "处理完成"
}
' students.csv
2. 错误处理
# 检查文件存在
awk '{
if(NF == 0) {
print "警告: 第" NR "行为空" > "/dev/stderr"
next
}
if($1 == "") {
print "错误: 第" NR "行缺少第一列" > "/dev/stderr"
exit 1
}
# 正常处理
print $1
}' file.txt
3. 可维护性
# 使用变量代替魔法数字
awk -v threshold=80 -v bonus=10 '
{
score = $2
if(score >= threshold) score += bonus
print $1, score
}' scores.txt
# 将配置放在BEGIN块
awk '
BEGIN {
# 配置参数
MIN_SCORE = 0
MAX_SCORE = 100
PASS_SCORE = 60
FS = ","
}
{
# 使用配置参数
if($2 < MIN_SCORE || $2 > MAX_SCORE) {
print "无效分数:", $0 > "/dev/stderr"
next
}
status = ($2 >= PASS_SCORE) ? "及格" : "不及格"
print $1, status
}' data.txt
记住:awk 是一个完整的编程语言,而不仅仅是一个命令行工具。掌握 awk 可以让你在文本处理任务中游刃有余,特别是需要字段操作、数据计算和复杂转换的场景。