Skip to content

awk

字数: 8072 字 时长: 30 分钟

awk 是一种强大的文本处理工具和编程语言,它可以逐行处理文本文件,并根据用户定义的模式和动作对文本进行操作。awk 处理文件时,将文件视为由记录(通常是行)和字段(通常是由空格或制表符分隔的列)组成。它可以方便地提取、过滤、转换和计算文本数据。

awk 的特点:

  • 功能强大:awk 是一种功能强大的文本处理工具,同时也是一种编程语言,用于在 Linux/Unix 系统中对文本和数据进行处理。它可以处理来自标准输入、一个或多个文件,或其他命令的输出。
  • 支持复杂操作:awk 支持用户自定义函数、动态正则表达式等高级功能,能够实现复杂的文本处理任务。
  • 面向记录和字段:awk 将输入的内容视为一系列的记录(行),每条记录又进一步分割为多个字段(列)。awk 通过内置变量 NR(记录数)和 NF(字段数)来管理和调用这些记录和字段的信息。

awk 的工作原理:

awk 扫描一个文本文件时,它会逐行读取内容,将每一行视为一条记录,然后根据指定的分隔符(默认为空白字符,如空格或制表符)将记录分割为多个字段。awk 通过内置变量和模式匹配来处理这些记录和字段。

基本语法

awk 的基本语法如下:

awk
awk [options] 'Pattern {Action}' file
常见选项含义
-F <fs>指定以 fs 作为输入行的分隔符,fs 是一个字符串或正则表达式。awk 命令默认分隔符为空格或者制表符。
f <file>从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令。
-v <var>=<value>在执行处理过程之前,设置一个变量并赋值。

模式-动作

awk 的核心是「模式-动作」结构,这是 awk 程序的基本单元。每条规则由这两部分组成。

模式(Pattern):

模式是一个条件,用于决定是否对当前行执行某个动作。模式可以是:

  • 正则表达式:如 /pattern/,用于匹配包含特定字符串或模式的行。例如 /apple/ 会匹配任何包含 apple 的行。
  • 关系表达式:使用比较运算符,如 $2 > 10 会匹配第二字段的值大于 10 的行。
  • 逻辑表达式:可以使用逻辑运算符 &&(与)、||(或)和 !(非)组合多个条件,如 $1 == "John" && $3 > 20 会匹配第一字段为 John 且第三字段大于 20 的行。
  • 特殊模式:如 BEGINENDBEGIN 模式在处理任何行之前执行,通常用于初始化变量;END 模式在处理完所有行之后执行,常用于计算汇总结果。

动作(Action):

动作是当模式匹配成功时执行的操作,由一系列的 awk 语句组成,用花括号 {} 括起来。常见的动作包括:

  • 打印操作:使用 printprintf 语句输出信息。例如 awk '{ print $1 }' file.txt,会打印 file.txt 中每一行的第一个字段。
  • 赋值操作:可以给变量赋值,例如 { sum += $2 } 会将第二字段的值累加到 sum 变量中。
  • 条件和循环语句:可以使用 if、else、while、for 等语句进行更复杂的逻辑控制。例如 awk '{ if ($2 > 50) print $1 }' file.txt 会打印第二字段大于 50 的行的第一个字段。

字段分割

awk 的核心特性之一就是自动字段分割

awk 会自动将每一行的文本分割成多个字段,并为每个字段分配一个变量(如 $1$2 等)。这种自动字段分割是 awk 强大的原因之一,因为它极大地简化了对结构化数据的处理。

awk 逐行读取输入文件时,它会根据预定义的字段分隔符(默认是空格或制表符)将每一行分割成多个字段。每个字段被自动分配到一个字段变量中:

  • $1:表示当前行的第一个字段。
  • $2:表示当前行的第二个字段。
  • $3:表示当前行的第三个字段。
  • $n:表示当前行的第 n 个字段。
  • $0:表示当前行的完整内容(所有字段的组合)。

默认情况下,awk 使用空格或制表符作为字段分隔符。这意味着连续的空格或制表符被视为一个分隔符。

假设有一个文件 data.txt,内容如下:

shell
Alice 30 New York
Bob,25,Los Angeles
Charlie 35 Chicago
David:40:San Francisco
Eve 22 Boston
Frank 33 Seattle
Grace,28,Phoenix
  1. 默认字段分隔符

    提取第 1 列和第 3 列(默认以空格或制表符分隔)。

    shell
    [root@localhost ~]# awk '{print $1, $3}' data.txt
    Alice New
    Bob,25,Los 
    Charlie Chicago
    David:40:San 
    Eve Boston
    Frank Seattle
    Grace,28,Phoenix

    在默认情况下,awk 使用空格或制表符作为字段分隔符。如果某行中没有这些分隔符(例如 Bob,25,Los AngelesDavid:40:San Francisco),awk 会将整行视为一个字段($1),而 $2$3 则是空的。

  2. 自定义字段分隔符

    可以通过 -F 选项或在程序中设置 FS 变量来自定义字段分隔符,例如使用逗号作为字段分隔符:

    shell
    [root@localhost ~]# awk -F, '{print $2,$3}' data.txt 
    
    25 Los Angeles
    
    28 Phoenix

    因为指定字段分隔符为逗号,所以没有其他没有逗号的行不存在第二字段和第三字段。

  3. 多种分隔符

    awk 也支持不同分隔符,像这样文件中包含多种分隔符(例如空格、逗号、冒号),可以使用正则表达式作为字段分隔符:

    shell
    [root@localhost ~]# awk -F'[ ,:]+' '{print $1, $3}' data.txt
    Alice New
    Bob Los
    Charlie Chicago
    David San
    Eve Boston
    Frank Seattle
    Grace Phoenix

    [ ,:]:这是一个字符集,表示匹配方括号里面任意一个字符;+:这是一个量词,表示前面的字符集 [ ,:] 可以出现一次或多次。换句话说,它会匹配一个或多个连续的空格、逗号或冒号。

  4. 字段变量的动态访问

    除了直接使用 $1$2 等字段变量,还可以通过变量动态访问字段。例如,提取最后一个字段:

    shell
    awk '{print $NF}' data.txt

    $NF 表示当前行的最后一个字段。NF 是一个内置变量,表示当前行的字段数。

  5. 字段分割和重新生成

    awk 允许直接修改字段变量的值,并且会自动更新整行内容($0),例如:

    shell
    [root@localhost ~]# awk -F'[ :,]+' '{$2 = $2 + 1; print}' data.txt
    Alice 31 New York
    Bob 26 Los Angeles
    Charlie 36 Chicago
    David 41 San Francisco
    Eve 23 Boston
    Frank 34 Seattle
    Grace 29 Phoenix

    在这个例子中,修改了 $2 的值,将第 2 个字段的值加 1。由于 $2 被修改了,awk 会自动重新生成 $0,即整行内容。print 会默认打印当前行的内容(即 $0)。

    除此之外,可以使用内置函数,例如修改文本大小写:

    shell
    [root@localhost ~]# awk -F'[ ,:]' '{ $3 = toupper($3); print }' data.txt
    Alice 30 NEW York
    Bob 25 LOS Angeles
    Charlie 35 CHICAGO
    David 40 SAN Francisco
    Eve 22 BOSTON
    Frank 33 SEATTLE
    Grace 28 PHOENIX

    将第 3 列改为大写。

  6. 提取字段并用竖号分隔输出

    shell
    [root@localhost ~]# awk -F'[ ,:]' -v OFS='|' '{print $1, $2, $3, $4}' data.txt
    Alice|30|New|York
    Bob|25|Los|Angeles
    Charlie|35|Chicago|
    David|40|San|Francisco
    Eve|22|Boston|
    Frank|33|Seattle|
    Grace|28|Phoenix|

    -v 选项用于在命令行中为 awk 脚本设置变量的值。它的作用是将外部的值传递给 awk 脚本中的变量。这样可以在脚本运行之前初始化变量,而不需要在脚本内部赋值。常用于设置输入分隔符(-F)、输出分隔符(OFS)、条件值等。

    OFS='|' 表示在输出时,用竖线(|)分隔字段。

  7. 条件匹配

    只打印第 2 列大于 25 的行:

    shell
    [root@localhost ~]# awk -F'[ ,:]' '$2 > 25 {print}' data.txt
    Alice 30 New York
    Charlie 35 Chicago
    David:40:San Francisco
    Frank 33 Seattle
    Grace,28,Phoenix

BEGIN

BEGINawk 中的一个特殊模式,用于在处理任何输入之前执行特定的动作。它通常用于初始化变量、设置格式或执行一些预处理操作。BEGIN 块在 awk 程序的开始处执行,且仅执行一次。

awk
BEGIN { action }
  • BEGIN:特殊模式,表示在处理任何输入之前执行。
  • action:在 BEGIN 块中要执行的动作,通常用 {} 包裹。

BEGIN 的用途:

  • 初始化变量:在处理输入之前设置初始值。
  • 设置格式:定义输出格式或字段分隔符。
  • 预处理操作:执行一些在处理输入之前需要完成的任务。

BEGINawk 的关键字,它必须大写。它会强制 awk 在读取数据前执行该关键字后指定的脚本命令。例如,可以在 BEGIN 块中初始化一些变量或输出头信息:

awk
awk 'BEGIN {
    # 设置字段分隔符为冒号
    FS = ":";
    # 打印表头
    print "用户名、t 主目录、t 登录 Shell";
}
{
    # 打印每一行的用户名、主目录和登录 Shell
    print $1, "\t", $6, "\t", $7;
}' /etc/passwd
text
用户名  主目录  登录 Shell
root     /root   /bin/shell
bin      /bin    /sbin/nologin
daemon   /sbin   /sbin/nologin
…………

在这个例子中,BEGIN 用于设置字段分隔符 FS 为冒号 :,并打印出标题行。其中 \t 为制表符(tab),以便在输出中对齐各列数据。

还可以在 BEGIN 模式中定义数组,用于存储数据或统计信息。例如,定义一个数组存储一些预定义的值:

awk
awk 'BEGIN {
    array["Alice"] = 25;
    array["Bob"] = 30;
    print array["Alice"];
}'

此命令将 Alice 映射到 25Bob 映射到 30,并打印 array["Alice"] 的值,输出为 25。

END

ENDawk 中的一个特殊模式,用于在处理完所有输入后执行特定的动作。它通常用于总结、打印统计结果或执行清理操作。END 块在 awk 程序的最后执行,且仅执行一次。

awk
END { action }
  • END:特殊模式,表示在处理完所有输入后执行。
  • action:在 END 块中要执行的动作,通常用 {} 包裹。

END 的用途:

  • 总结和打印统计结果:在处理完所有输入后,输出一些汇总信息。
  • 执行清理操作:例如关闭文件、释放资源等。
  • 检查条件:在处理完所有数据后,检查某些条件是否满足。
awk
awk '
{
    print;
}
END {
    print "行数:", NR;
}' /etc/passwd
text
root:x:0:0:root:/root:/bin/shell
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
……
行数:20

可以看到,当 awk 程序先执行 print 打印完文件内容后,才会执行 END 中的脚本命令。如果没有 print 则不会打印文件内容。

变量

awk 的脚本程序中,支持使用变量来存取值。awk 支持两种不同类型的变量:

  • 内建变量:awk 本身就创建好,用户可以直接拿来用的变量,这些变量用来存放处理数据文件中的某些字段和记录的信息。
  • 自定义变量:awk 支持用户自己创建变量。

内置变量

awk 程序使用内建变量来引用程序数据里的一些特殊功能。常见的一些内建变量:

常见变量功能
FIELDWIDTHS用于指定固定宽度字段的宽度。
NR表示输入的当前记录编号。(即行号)。
FNR表示当前输入文件中的记录编号(即行号)。它与 NR 变量类似,但 NR 表示所有输入文件中的记录编号,而 FNR 仅表示当前文件中的记录编号。
NF表示当前记录中的字段数。
FS表示输入字段分隔符,默认为空格。
RS表示输入记录分隔符,默认为换行符(\n)。
OFS表示输出字段分隔符,默认为空格。
ORS表示输出记录分隔符,默认为换行符(\n)。

其他变量:

其他变量功能
ARGC命令行参数个数。
ARGIND当前文件在 ARGC 中的位置。
ARGV包含命令行参数的数组。
CONVFMT数字的转换格式,默认值为 %.6g。
ENVIRON当前 shell 环境变量及其值组成的关联数组。
ERRNO当读取或关闭输入文件发生错误时的系统错误号。
FILENAME当前输入文档的名称。
IGNORECASE设成非 0 值时,忽略 awk 命令中出现的字符串的字符大小写。
OFMT数字的输出格式,默认值为 %.6g。
RLENGTH由 match 函数所匹配的子字符串的长度。
RSTART由 match 函数所匹配的子字符串的起始位置。

FS 和 OFS

变量 FSOFS 定义了 awk 如何处理数据流中的数据字段。变量 FS 来定义记录中的字段分隔符,变量 OFS 具备相同的功能,只不过是用在输出 print 命令的输出上,例如:

awk
awk 'BEGIN {
    FS = ":";
    OFS = ",";
}
{
    print $1, $2, $3;
}' /etc/passwd
text
root,x,0
bin,x,1
daemon,x,2

这个例子中的 awk 命令用于处理 /etc/passwd 文件。它首先在 BEGIN 块中设置了 FSOFS 的值。

FS 的值设置为冒号 :,意味着 awk 会将输入数据中的冒号作为字段分隔符。将 OFS 的值设置为逗号 ,,意味着 awk 会在输出数据中使用逗号作为字段分隔符。

然后,对于 /etc/passwd 文件中的每一行数据,awk 都会执行 { print $1, $2, $3 } 中的操作,打印出该行的前三个字段。由于设置了 OFS 的值,所以在输出数据中,各个字段之间会用逗号 , 分隔。

FIELDWIDTHS

FIELDWIDTHS 变量允许用户不依靠字段分隔符来读取记录。在一些应用程序中,数据并没有使用字段分隔符,而是被放置在了记录中的特定列,这种情况下,必须设定 FIELDWIDTHS 变量来匹配数据在记录中的位置。

一旦设置了 FIELDWIDTH 变量,awk 就会忽略 FS 变量,并根据提供的字段宽度来计算字段,下面是个采用字段宽度而非字段分隔符的例子:

text
13111111111
15011111111
17211111111
18311111111
awk
awk 'BEGIN {
    FIELDWIDTHS = "3 4 4";
}
{
    print $1, $2, $3;
}' iphone.txt
text
131 1111 1111
150 1111 1111
172 1111 1111
183 1111 1111

注意,FIELDWIDTHS 适用于固定宽度的字段,每个字段的宽度是固定的,如果字段宽度不固定,就无法正确分割字段。

RS 和 ORS

变量 RSORS 定义了 awk 程序如何处理数据流中的字段,默认情况下,awkRSORS 设为换行符。默认的 RS 值表明,输入数据流中的每行新文本就是一条新纪录。有时,会在数据流中碰到占据多行的字段。

典型的例子是包含地址和电话号码的数据,其中地址和电话号码各占一行,例如:

text
4 Jersey St, Boston, MA 02215
Fenway Park
+18777337699

Anaheim, CA 92802
Disneyland Park
+17147814636

1525 Bernice St, Honolulu, HI 96817 美国
Bishop Museum
+18088473511
awk
awk 'BEGIN {
    FS = "\n";
    RS = "";
    ORS = "\n\n";
}
{
    print $1, $2, $3;
}' address.txt
text
4 Jersey St, Boston, MA 02215 Fenway Park +18777337699

Anaheim, CA 92802 Disneyland Park +17147814636

1525 Bernice St, Honolulu, HI 96817 美国 Bishop Museum +18088473511

BEGIN{FS="\n";RS="";ORS="\n\n"}

  • FS="\n":设置字段分隔符为换行符 \n,每行是一个字段。
  • RS="":设置记录分隔符为空字符串,记录由空行分隔。
  • ORS="\n\n":设置输出记录分隔符为两个换行符,每个输出记录之间会有一个额外的空行。

通过设置 ORS,可以控制输出记录之间的分隔符,从而调整输出的格式。这在需要在输出中添加额外的空行或特殊分隔符时非常有用。

FNR 和 NR

FNRNR 变量虽然类似,但又略有不同。FNRNR 都表示当前记录的编号,但它们的区别在于

  • NR:表示从 awk 开始执行程序后所读取的数据行数,是一个全局变量,对所有输入文件的行进行累计计数。
  • FNR:表示当前文件中已经读入的记录数,是一个局部变量,每读取一个新文件时,FNR 会重新从 1 开始计数。

假设有两个文件 file1 和 file2,它们的内容分别为:

text
Alice 30 New York
Bob 25 Los Angeles
text
Charlie 35 Chicago
David 40 San Francisco

现在运行下面这个 awk 命令,这个命令会输出每一行的 FNR 值、NR 值和内容:

awk
awk '
{
    print FNR, NR, $0;
}' nr.txt fnr.txt
text
1 1 Alice 30 New York
2 2 Bob 25 Los Angeles
1 3 Charlie 35 Chicago
2 4 David 40 San Francisco

可以看到,对于每个文件FNR 都是从 1 开始计数的,而 NR 则是从第一个文件开始一直累加到最后一个文件。这就是 FNRNR 的区别。

自定义变量

和其他典型的编程语言一样,awk 允许用户定义自己的变量在脚本程序中使用。awk 自定义变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。更重要的是,awk 变量名区分大小写。

定义自定义变量非常简单,只需要在使用变量之前给它赋一个初始值即可。例如:

awk
awk 'BEGIN {
    var_test = "10";
    print var_test;
}'
text
10

上面这个例子中,定义了一个名为 var_test 的自定义变量,并给它赋了初始值 10。然后使用 print 语句输出了这个变量的值。

awk 中,变量的类型是动态的,也就是说,变量的类型会根据它所存储的值的类型而改变。例如,如果给一个变量赋了一个字符串值,那么这个变量就是字符串类型的;如果给一个变量赋了一个数字值,那么这个变量就是数字类型的。例如:

awk
awk 'BEGIN {
    var_test = "10";
    print var_test;
    var_test = "Hello, awk!";
    print var_test;
}'
text
10
Hello,awk!

awk 还可以给程序中的变量赋值,这允许在正常的代码之外赋值,即时改变变量的值,例如:

text
111 222 333
444 555 666
777 888 999
awk
awk '{print $n}' n=2 var.txt
text
222
555
888

在这个例子中,awk 程序会打印出文件 var.txt 中每一行的第 n 个字段,也就是第二个字段。

需要注意的是,这种方法只能在 awk 程序的主体部分中使用,不能在 BEGIN 块中使用。因为在 BEGIN 块中,awk 还未开始处理输入文件,因此 $n 无法引用具体的字段。

如果需要在 BEGIN 块中使用变量,可以使用 -v 选项在脚本运行之前为变量赋值:

awk
awk -v n=2 'BEGIN {
    print "Column:", n;
}
{
    print $n;
}' var.txt
text
Column: 2
222
555
888

在这个例子中,在脚本运行之前,使用 -v n=2 将变量 n 初始化为 2。然后,在 awk 程序的 BEGIN 块中,打印出变量 n 的值。在 awk 程序的主体部分,打印出文件 var.txt 中每一行的第 n 个字段,也就是第二个字段。

关联数组

awk 中,数组是关联数组(Associative Arrays),这意味着数组的索引可以是数字或字符串,而不是像传统数组那样仅限于连续的数字索引。

数组

数组是一种数据结构,用于存储一系列的值。这些值可以是数字、字符串或其他类型的数据。数组中的每个值都有一个唯一的索引(或键),用于访问该值。

awk 关联数组的特点:

  • 索引可以是数字或字符串:在 awk 中,数组的索引不仅可以是数字,还可以是任意字符串。这种灵活性使得 awk 的数组非常强大。
  • 无需提前声明大小awk 的数组不需要提前声明大小,它会在运行时根据需要自动扩展或收缩。
  • 索引的唯一性:每个索引字符串必须唯一地标识出赋给它的数据元素。如果尝试为同一个索引赋值多次,后面的值会覆盖前面的值。
  • 遍历顺序不固定:关联数组的遍历顺序是随机的,不保证与插入顺序一致。

数组定义和使用

awk 脚本程序中,定义一个数组变量可以使用标准复制语句,其基本格式为:

awk
array_name[index]=value
  • array_name:数组名称。
  • index:数组索引。
  • value 是数组中元素所赋予的值。

例如:

awk
fruits["芒果"] = "橘色";
fruits["橘子"] = "黄色";
fruits["苹果"] = "红色";

这里,fruits 是数组名,芒果橘子 是索引(键),橘色黄色 是对应的值。

注意,在引用数组变量时,必须用索引值(index)来提取相应的数据元素值,例如:

awk
awk 'BEGIN {
    fruits["芒果"] = "橘色"; 
    fruits["橘子"] = "黄色"; 
    print fruits["芒果"];
    print fruits["橘子"];
}'
text
橘色
黄色

数组变量也是变量,也可以使用其进行基本的算术运算,例如:

awk
awk 'BEGIN {
    num[1] = 10;
    num[2] = 20;
    sum = num[1] + num[2];
    print sum;
}'
text
30

关联数组的遍历

awk 中遍历关联数组,可以用 for 语句的一种特殊形式:

awk
for (variable in array) {
    statements
}
  • variable 是循环变量,它会在每次迭代时被赋值为数组的一个索引。
  • array 是要遍历的关联数组。

在循环体中,可以使用 array[variable] 来访问当前索引对应的元素。注意的是,整个遍历过程中,传给 variable 的都是每个数组元素的索引值(也就是 index),不是数组元素的值。

例如:

awk
awk 'BEGIN {
    fruits["芒果"] = "橘色";
    fruits["橘子"] = "黄色";
    fruits["苹果"] = "红色";
    for (key in fruits) {
        print "水果:", key,"," "颜色:", fruits[key];
    }
}'
text
水果:苹果 ,颜色:红色
水果:芒果 ,颜色:橘色
水果:橘子 ,颜色:黄色

需要注意的是,关联数组中元素的顺序是不确定的。因此,遍历关联数组时,元素的顺序也是不确定的。

删除数组变量

awk 脚本程序还支持从关联数组中删除某个数组索引,使用 delete 命令就可以,此命令会从数组中删除指定的索引值及相关的数据元素的值。其基本格式如下:

awk
delete array[index]
  • array 是要删除元素的数组。
  • index 是要删除的元素的索引。

例如:

awk
awk 'BEGIN {
    fruits["芒果"] = "橘色";
    fruits["橘子"] = "黄色";
    fruits["苹果"] = "红色";
    delete fruits["苹果"];
    for (key in fruits) {
        print "水果:", key,"," "颜色:", fruits[key];
    }
}'
text
水果:芒果 ,颜色:橘色
水果:橘子 ,颜色:黄色

除此之外,还可以使用 delete 语句来删除整个数组,例如:

awk
awk 'BEGIN {
    fruits["芒果"] = "橘色";
    fruits["橘子"] = "黄色";
    fruits["苹果"] = "红色";
    delete fruits;
    for (key in fruits) {
        print "水果:", key,"," "颜色:", fruits[key];
    }
}'

这段代码不会输出任何内容,因为整个数组都被删除了。

使用分支结构

awk 支持标准的 if-then-else 格式的 if 语句,其基本格式为:

awk
if (condition)
    statement1
else
    statements2

也可以将它放在一行上,像这样:

awk
if (condition) statement1;else statement2

例如,根据日志关键词标记错误级别(ERROR/WARNING/INFO):

log
[2023-10-01 08:00] INFO Server started
[2023-10-01 08:05] WARNING High memory usage
[2023-10-01 08:10] ERROR Database connection failed
[2023-10-01 08:15] INFO User login
awk
awk '{
    if ($3 == "ERROR") {
        color = "\033[31m"  # 红色
    } else if ($3 == "WARNING") {
        color = "\033[33m"  # 黄色
    } else {
        color = "\033[32m"  # 绿色
    }
    printf "%s[%-8s]\033[0m %s\n", color, $3, $0
}' app.log

这段 awk 代码的主要功能是根据日志文件 app.log 中每行的第三个字段(即日志级别),使用不同的颜色(红色、黄色或绿色)来标记该日志级别,并将整行日志信息输出。

text
[INFO    ] [2023-10-01 08:00] INFO Server started
[WARNING ] [2023-10-01 08:05] WARNING High memory usage
[ERROR   ] [2023-10-01 08:10] ERROR Database connection failed
[INFO    ] [2023-10-01 08:15] INFO User login

printf

printf 是一种非常强大的输出格式化操作符,常用于在程序中控制输出的格式。它允许以高度可读和标准化的方式格式化文本和数据。

使用循环结构

awk 提供了三种主要的循环结构:whiledo-whilefor。这些循环结构允许重复执行特定的操作,非常适合处理文本文件中的多行数据或执行重复性任务。

while

while 循环的语法如下:

awk
while (condition) {
    statements
}

condition 是一个布尔表达式。在每次迭代开始时,都会检查 condition 的值。如果 condition 的值为真,那么 while 循环后面的语句块中的语句就会被执行;否则,循环就会结束。

特点:

  • 在每次迭代开始时检查条件。
  • 如果条件为真,执行循环体。
  • 适合在条件不满足时结束循环。

例如,统计数字 1 到 10 的平方和

awk
awk 'BEGIN {
    sum = 0;
    i = 1;
    while (i <= 10) {
        sum += i * i;
        i++;
    }
    print "1 到 10 的平方和为:", sum;
}'
shell
1 10 的平方和为: 385

do-while

do-while 循环确保循环体至少执行一次,然后在每次迭代结束时检查条件。它的语法如下:

awk
do {
    statements
} while (condition)

condition 是一个布尔表达式。与 while 循环不同的是,在每次迭代结束时才会检查 condition 的值。这意味着,无论 condition 的初始值是什么,do-while 循环都至少会执行一次。

特点:

  • 循环体至少执行一次。
  • 适合在条件不满足时结束循环。

例如,下面的代码会使用 do-while 循环来打印数字 1 到 5:

awk
awk 'BEGIN {
    i = 1;
    do {
        print i;
        i++;
    } while (i <= 5)
}'
text
1
2
3
4
5

for

for 循环是常用的循环结构,适合在已知迭代次数的情况下使用。它的语法如下:

shell
for (initialization; condition; increment) {
    statements
}

其中,initialization 是一个表达式,它会在循环开始之前执行一次。condition 是一个布尔表达式,在每次迭代开始时都会检查它的值。如果 condition 的值为真,那么 for 循环后面的语句块中的语句就会被执行;否则,循环就会结束。在每次迭代结束时,都会执行一次 increment 表达式。

特点:

  • 初始化表达式在循环开始前执行一次。
  • 条件表达式在每次迭代开始时检查。
  • 增量表达式在每次迭代结束时执行。

例如,打印九九乘法表:

awk
awk 'BEGIN {
    print "九九乘法表:";
    for (i = 1; i <= 9; i++) {
        for (j = 1; j <= i; j++) {
            printf "%d×%d=%-3d", j, i, i*j;
        }
        print "";
    }
}'
text
九九乘法表:
1×1=1  
1×2=2  2×2=4  
1×3=3  2×3=6  3×3=9  
1×4=4  2×4=8  3×4=12 4×4=16 
1×5=5  2×5=10 3×5=15 4×5=20 5×5=25 
1×6=6  2×6=12 3×6=18 4×6=24 5×6=30 6×6=36 
1×7=7  2×7=14 3×7=21 4×7=28 5×7=35 6×7=42 7×7=49 
1×8=8  2×8=16 3×8=24 4×8=32 5×8=40 6×8=48 7×8=56 8×8=64 
1×9=9  2×9=18 3×9=27 4×9=36 5×9=45 6×9=54 7×9=63 8×9=72 9×9=81

break 和 continue

除此之外,awk 还支持使用 breakcontinue 关键字:

  • break:用于立即跳出当前循环。
  • continue:用于跳过当前循环的剩余部分,直接开始下一次循环。
  1. 使用 break 跳出循环

    break 用于跳出当前循环,终止循环的执行:

    awk
    awk 'BEGIN {
        for (i = 1; i <= 9; i++) {
            if (i == 5) {
                break;  # 在遇到数字 5 时跳出循环
            }
            print i;
        }
    }'
    text
    1
    2
    3
    4
    6
    7
    8
    9

    使用 for 循环遍历数字 1 到 9,如果当前数字等于 5,使用 break 终止循环,也就是说其他数字正常打印,但在遇到数字 5 后停止。

  2. 使用 continue 跳过当前循环

    continue 跳过当前循环的剩余部分,直接进入下一次循环:

    awk
    awk 'BEGIN {
        for (i = 1; i <= 9; i++) {
            if (i == 5) {
                continue;  # 跳过数字 5
            }
            print i;
        }
    }'
    text
    1
    2
    3
    4
    6
    7
    8
    9

    使用 for 循环遍历数字 1 到 9,如果当前数字等于 5,使用 continue 跳过当前循环,直接进入下一次循环。其他数字正常打印。

函数

awk 中,函数是一种可以重复使用的代码块,它可以接受一些输入参数,执行特定的操作,并返回一个结果。函数是 awk 脚本的核心组件之一,用于封装和复用代码,使脚本更加模块化和易于维护。

内置函数

和内建变量类似,awk 提供了许多内置函数,用于执行常见的操作,如字符串处理、数学计算和时间日期处理。

字符串函数,用于处理和转换字符串:

字符串函数介绍示例
toupper(string)将字符串中的所有小写字母转换为大写字母。awk '{print toupper($1)}'
tolower(string)将字符串中的所有大写字母转换为小写字母。awk '{print tolower($1)}'
length(string)返回字符串的长度。如果未提供参数,则返回 $0 的长度。awk '{print length($1)}'
substr(string, start, length)返回字符串的子字符串,从 start 开始,长度为 lengthawk '{print substr($1, 1, 3)}'
index(string, target)返回子字符串 target 在字符串 string 中的位置,未找到时返回 0。awk '{print index($1, "li")}'
match(string, regex)返回正则表达式 regex 在字符串 string 中匹配的位置,未找到时返回 0。awk '{print match($1, /li/)}'
split(string, array, separator)将字符串按分隔符分割,并将结果存储到数组中。返回分割后的字段数。awk '{split($1, arr, ""); print arr[1], arr[2]}'

数值函数,用于数学运算:

数值函数介绍示例
int(x)返回数值的整数部分。awk '{print int($2)}'
sqrt(x)返回数值的平方根。awk '{print sqrt($2)}'
sin(x)返回数值的正弦值。awk '{print sin($2)}'
cos(x)返回数值的余弦值。awk '{print cos($2)}'
atan2(y, x)返回 y/x 的反正切值。awk '{print atan2($2, $3)}'
exp(x)返回 ex 次幂。awk '{print exp($2)}'
log(x)返回数值的自然对数。awk '{print log($2)}'

输入输出函数,用于控制数据的读取和格式化输出:

输入输出函数介绍示例
printf(format, ...)格式化输出,类似于 C 语言中的 printfawk '{printf "Name: %-10s Age: %d\n", $1, $2}'
sprintf(format, ...)将格式化的字符串存储到变量中。awk '{msg = sprintf("Name: %-10s Age: %d", $1, $2); print msg}'
close(filename)关闭文件或管道。awk '{print $1 > "output.txt"} END {close("output.txt")}'
getline从文件或管道中读取下一行。awk '{getline nextline < "data.txt"; print nextline}'

其他函数,如随机数生成、系统命令执行等:

其他函数介绍示例
srand(seed)设置随机数种子。awk 'BEGIN {srand(123); print rand()}'
rand()返回 0 到 1 之间的随机数。awk 'BEGIN {print rand()}'
system(command)执行外部命令。awk 'BEGIN {system("date")}'

自定义函数

awk 中,可以定义自己的函数,这些函数可以接受参数并返回值。定义函数的基本格式如下:

awk
function function_name(argument1, argument2, ...) {
    # 函数体
    function body
}
  • function_name:函数的名称,以字母开头,可以包含字母、数字或下划线。不能使用 awk 的保留关键字作为函数名。
  • argument1, argument2, ...:函数的参数,通过逗号分隔。参数是可选的,也可以定义没有参数的函数。
  • function body:函数体,包含 awk 程序代码。
  1. 定义一个简单的函数

    用于打印记录中的第三个字段:

    awk
    function print_third() {
        print $3
    }
  2. 定义一个返回值的函数

    awk
    function myrand(limit) {
        return int(limit * rand())
    }

    用于返回一个随机整数。

awk 允许将多个函数存储在一个库文件中,这样可以在多个 awk 脚本中复用这些函数,创建一个文件 functions.awk,存储所有函数:

awk
# 返回两个数字的和
function add(num1, num2) {
    return num1 + num2
}

# 返回两个数字的差
function subtract(num1, num2) {
    return num1 - num2
}

# 返回两个数字的乘积
function multiply(num1, num2) {
    return num1 * num2
}

# 返回两个数字的商
function divide(num1, num2) {
    if (num2 != 0) {
        return num1 / num2
    } else {
        return "Error: Division by zero"
    }
}

# 主函数
function main(num1, num2) {
    # 计算和
    result_add = add(num1, num2)
    print "Sum =", result_add
    # 计算差
    result_subtract = subtract(num1, num2)
    print "Difference =", result_subtract
    # 计算乘积
    result_multiply = multiply(num1, num2)
    print "Product =", result_multiply
    # 计算商
    result_divide = divide(num1, num2)
    print "Quotient =", result_divide
}

创建一个脚本文件 script.awk,调用库文件中的函数:

awk
BEGIN {
    main(10, 5)
}

运行脚本时,需要使用 -f 选项加载库文件和脚本文件:

shell
[root@localhost ~]# awk -f functions.awk -f script.awk
Sum = 15
Difference = 5
Product = 50
Quotient = 2