速通 awk:一篇文章带你理解 awk 原理,大量实战案例让你马上成为 awk 专家

基本原理

  • 每次读取一条记录。
  • 记录分隔符 :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 尝试识别扩展符。
  • BEGINEND 是两个特殊模式,分别在读取第一条记录和最后一条记录时执行对应动作,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

参考

相关推荐
Lyre丶3 小时前
Ubuntu 24.04 LTS 安装GAMIT
linux·经验分享·学习·ubuntu·gamit
namekong83 小时前
ubuntu 通过下面几种方式查看系统 重启时间/开机时间:
linux·运维·ubuntu
爱奥尼欧3 小时前
【Linux】网络部分——网络基础(协议与网络传输)
linux·网络·arm开发
_dindong3 小时前
Linux系统编程:线程概念
linux·运维·笔记·学习
雪饼android之路4 小时前
【Android】 android suspend/resume总结(3)
android·linux
老黄编程5 小时前
ubuntu如何查看一个内核模块被什么模块依赖(内核模块信息常用命令)?
linux·运维·ubuntu
知北游天5 小时前
Linux网络:使用UDP实现网络通信(服务端&&客户端)
linux·网络·udp
半桔5 小时前
【网络编程】TCP 粘包处理:手动序列化反序列化与报头封装的完整方案
linux·网络·c++·网络协议·tcp/ip
<但凡.5 小时前
Linux 修炼:进程控制(一)
linux·运维·服务器·bash