速通 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

参考

相关推荐
HLC++10 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室12 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy14 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕15 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生16 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1118 小时前
linux基础IO
linux·运维·服务器
BelongPanda19 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯20 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983420 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器