基本原理
- 每次读取一条记录。
- 记录分隔符 :record seperator,
RF
表示,默认换行符区分。 - 记录数 :
NR
,每当读取一条记录该变量值加一,因此它包含了输入流的总记录数。 - 文件记录数 :
FNR
,File Record Number。 - 输出记录分隔符 :
ORS
,Output Record Seperator,默认值为一个换行符。 - 每条记录自动分割为域(fields),
$
表示域,$1
表示第一个域,$0
表示整条记录(在 awk 中,$
表示「域」,而不是 shell 中的变量扩展)。 - 域分隔符 :field sperator,
FS
表示,默认分隔符为空格,假设要使用:
作为域分隔符,使用FS = ":"
设置,也可以在命令行中-F:
来设置。 - 域数 :
NF
变量为域的总数,$NF
表示最后一个域。 - 输出域分隔符 :
OFS
,Output Field Seperator,默认为空格。 - 每条记录可通过模式(pattern)进行测试是否匹配(可以省略),然后执行相应的动作(action)。
- awk 程序由
awk
命令和单引号组成。单引号很重要,因为我们不想 shell 尝试识别扩展符。 BEGIN
和END
是两个特殊模式,分别在读取第一条记录和最后一条记录时执行对应动作,BEGIN
可以用于初始化变量、输出页眉,END
可以用于输出页脚和统计总结信息等。{
必须跟在模式后面,与模式同一行。- awk 也可以跟 shell 一样,写成脚本调用:
ls -l /usr/bin | awk -f link.awk
,在文件开头注明:#!/usr/bin/awk -f
- 文件名 :
FILENAME
变量表示。
基本语法
- 注释以
#
开头 \
换行print
使用,
分隔参数- 可以使用
==
判断字符串,可以运行*
+
等算数运算 - 支持正则表达式:
expression ~ /regexp/
- 支持
&&
和||
逻辑运算符
bash
赋值 = += -= *= /= %= ^= ++ --
关系 < > <= >= == !=
算术 + - * / % ^
匹配 ~ !~
数组 in
逻辑 && ||
bash
BEGIN { # 动作的起始中括号必须和模式在同一行
# 空白行会被忽略
# 反斜杠可以用于分隔很长的行
print \
$1, # 参数列表必须以逗号分隔
$2, # 注释可以出现在任意行的末尾
$3
# 多条声明使用分号分隔时可以出现在同一行
print "String 1"; print "String 2"
} # 动作的关闭中括号
基础示例:
bash
# 判断
$1 == "Fedora"
# 逻辑运算
$1 > 100 && $NF == "Debit"
# 正则匹配:~ 表示「匹配」或「包含」(正则关键词),因此我们可以将下面读为「第三个域匹配正则表达式」^[567]
$3 ~ /^[567]/
# 设置域分隔符
BEGIN { FS = ":" }
{ print $1, $5 }
# 类型
# + 0 强制转换为数字
n = 105 + 0
# 空字符串拼接,强制转换为字符串
s = 105 ""
# if else
awk 'BEGIN {if (1) print "true"; else print "false"}'
数组用法
数组是无序的:
bash
# 对数组元素赋值
a[1] = 5 # Numeric index
a["five"] = 5 # String index
# 多维数组
a[j,k] = "foo"
# 元素是否在数组中
(var in array)
# 检查索引 var 是否在指定数组 array 中
if (array[var] != "")
# 删除数组及数组元素
delete a[i] # delete a single element
delete a # delete array
# 遍历数组索引示例:
awk 'BEGIN {for (i=0; i<10; i++) a[i]="foo"; for (i in a) print i}'
实战案例
简单示例
bash
# 逐行输出 /usr/bin 目录,省略 pattern
$ ls -l /usr/bin | awk '{print $0}'
# 输出域超过 9 的行(含符号链接的行)
$ ls -l /usr/bin | awk 'NF > 9 {print $0}'
# 输出示例
lrwxrwxrwx 1 root root 21 Feb 4 2025 awk -> /etc/alternatives/awk
# BEGIN 和 END 模式使用示例
ls -l /usr/bin | awk '
BEGIN {
print "Directory Report"
print "================"
}
NF > 9 {
print $9, "is a symbolic link to", $NF
}
END {
print "============="
print "End Of Report"
}
'
# 指定域分隔符
awk -F: '{print $1, $3}' /etc/passwd
# 读取你的目录列表并将其输出为 CSV 流:
ls -l | awk 'BEGIN {OFS = ","}
NF == 9 {print $1,$2,$3,$4,$5,$6,$7,$8,$9}'
# 将输出记录符设置为两个换行符来使文件变为双倍行距:
ls -l | awk 'BEGIN {ORS = "\n\n"} {print}'
# 计算文件平均大小
ls -l /usr/bin | awk 'NF >=9 {c++; t += $5} END {print t / c}'
# 反序输出域
ls -l | awk '{s = ""; for (i = NF; i > 0; i--) s = s $i OFS; print s}'
# 反序输出域 while 示例
ls -l | awk '{
s = ""
i = NF
while (i > 0) {
s = s $i OFS
i--
}
print s
}'
# 模拟 cat 命令
awk '{print $0}' file1 file2 file3
脚本格式调用
awk 脚本:link.awk
bash
#!/usr/bin/awk -f
# Print a directory report
BEGIN {
print "Directory Report"
print "================"
}
NF > 9 {
print $9, "is a symbolic link to", $NF
}
END {
print "============="
print "End Of Report"
}
调用:
bash
ls -l /usr/bin | awk -f link.awk
每隔 60 行添加一个分页符
bash
ls -l /usr/bin | awk '
BEGIN {
page_length = 60
}
{
if (NR % page_length)
print
else
print "\f" $0
}
'
统计文件类型数量
bash
ls -l /usr/bin | awk '
$1 ~ /^-/ {t["Regular Files"]++}
$1 ~ /^d/ {t["Directories"]++}
$1 ~ /^l/ {t["Symbolic Links"]++}
END {for (i in t) print i ":\t" t[i]}
'
输出到文件:
bash
ls -l /usr/bin | awk '
$1 ~ /^-/ {print $0 > "regfiles.txt"}
$1 ~ /^d/ {print $0 > "directories.txt"}
$1 ~ /^l/ {print $0 > "symlinks.txt"}
'
通过管道对数组排序
读取目录到一个数组中,并将数组输出:
bash
ls -l /usr/bin | awk '
$1 ~ /^-/ {a[$9] = $5}
END {for (i in a)
{print a[i] "\t" i | "sort -nr"}
}
'
模拟 wc 命令
bash
awk '{chars += length($0); words += NF}
END {print NR, words, chars + NR}' file1
给一个文件插入页头和页尾
bash
awk '
BEGIN {
while (getline <"header.txt" > 0) {
print $0
}
}
{print}
END {
while (getline <"footer.txt" > 0) {
print $0
}
}
' < body.txt > finished_file.txt
生成随机整数
bash
# random_table.awk - generate table of random numbers
function rand_integer(max) {
return int(max * rand()) + 1
}
BEGIN {
srand()
for (i = 0; i < 100; i++) {
for (j = 0; j < 5; j++) {
printf(" %5d", rand_integer(99999))
}
printf("\n", "")
}
}
bash
awk -f random_table.awk > random_table.dat
将文件转换为 csv/tsv 格式
使用 TSV 格式可以避免 CSV 格式引号中的逗号问题。
bash
awk 'BEGIN {OFS=","} {print $1,$2,$3,$4,$5}' random_table.dat
awk 'BEGIN {OFS="\t"} {print $1,$2,$3,$4,$5}' random_table.dat
按扩展名统计各类文件数量
bash
# file_types.awk - sorted list of file name extensions and counts
BEGIN {FS = "."}
{types[$NF]++}
END {
for (i in types) {
printf("%6d %s\n", types[i], i) | "sort -nr"
}
}
要找到家目录中最多的 10 个扩展名,我们可以这样使用该程序:
bash
find ~ -name "*.*" | awk -f file_types.awk | head