Sed&awk笔记之awk篇:快速了解Awk(二)

    xiaoxiao2026-02-08  0

    上一篇文章中,我们从awk使用的命令行语法入手,简单介绍了它是如何使用的。在这一篇里,我们要介绍awk编程语言的基本元素和概念,简单介绍它是如何处理文本的。

    记录(Record)与字段(Field)

    对于数据库来说,一个数据库表是由多条记录组成的,每一行表示一条记录(Record)。每条记录由多列组成,每一列表示一个字段(Field)。Awk将一个文本文件视为一个文本数据库,因此它也有记录和字段的概念。默认情况下,记录的分隔符是回车,字段的分隔符是空白符,所以文本文件的每一行表示一个记录,而每一行中的内容被空白分隔成多个字段。利用字段和记录,awk就可以非常灵活地处理文件的内容。 可以通过-F选项来修改默认的字段分隔符,例如/etc/passwd的每一行都是由冒号分隔成多个字段的,所以这里就需要将分隔符设置成冒号: [kodango@devops awk_temp]$ awk -F: '{print $1}' /etc/passwd | head -3 root bin daemon 这里通过$1引用第一人字段,类似地$2表示第二个字段,$3表示第三个字段.... $0则表示整个记录。内置变量NF记录着字段的个数,所以$NF表示最后一个字段: [kodango@devops awk_temp]$ awk -F: '{print $NF}' /etc/passwd | head -3 /bin/bash /bin/false /bin/false 当然,$(NF-1)表示倒数第二个。 内置变量FS也可以用于更改字段分隔符,它记录着当前的字段分隔符: [kodango@devops awk_temp]$ awk -F: '{print FS}' /etc/passwd | head -1 : [kodango@devops awk_temp]$ awk -v FS=: '{print $1}' /etc/passwd | head -1 root 记录的分隔符可以通过内置变量RS更改: [kodango@devops awk_temp]$ awk -v RS=: '{print $0}' /etc/passwd | head -1 root 如果将RS设置成空,行为有就一点怪异了,它会将连续不为空行的所有行(一个段落)当作一个记录,而且强制回车为字段分隔符: [kodango@devops awk_temp]$ cat awk_man.txt The awk utility shall execute programs written in the awk programming language, which is specialized for textual data manipulation. An awk program is a sequence of patterns and corresponding actions. When input is read that matches a pattern, the action associated with that pattern is carried out. Input shall be interpreted as a sequence of records. By default, a record is a line, less its terminating , but this can be changed by using the RS built-in variable. Each record of input shall be matched in turn against each pattern in the program. For each pattern matched, the associated action shall be executed. [kodango@devops awk_temp]$ awk 'BEGIN {RS="";FS=":"} {print "First line: " $1}' awk_man.txt First line: The awk utility shall execute programs written in the awk programming language, First line: Input shall be interpreted as a sequence of records. By default, a record is a line, 这里,我们将变量赋值放到 BEGIN动作中执行,因为 BEGIN动作是在文件处理之前执行的,专门用于放初始化的语句。FS的赋值在这里是无效的,awk依然使用回车符来分隔字段。

    脚本(Script)组成

    命令行中的program部分,可以称为awk代码,也可以称为awk脚本。一段awk脚本是由多个' pattern { action }'序列组成的。action是一个或者多个语句,它在输入行匹配pattern的时候被执行。如果pattern为空,表明这个action会在每一行处理时都会被执行。下面的例子简单地打印文件的每一行,这里不带任何参数的print语句打印的是整个记录,类似' print $0': [kodango@devops awk_temp]$ echo -e 'line1\nline2' | awk '{print}' line1 line2 除了 pattern { action },还可以在脚本中定义自定义的函数,函数定义格式如下所示: function name(parameter list) { statements } 函数的参数列表用逗号分隔,参数默认是局部变量,无法在函数之外访问,而在函数中定义的变量为全局变量,可以在函数之外访问,如: [kodango@devops awk_temp]$ echo line1 | awk ' function t(a) { b=a; print a; } { print b; t("kodango.me"); print b; }' kodango.me kodango.me Awk脚本中的语句使用空行或者分号分隔,使用分号可以放在同一行,不过有时候会影响可读性,尤其是分支或循环结构中,很容易出错。 如果Awk中的一个语句太长,要分成多行,可以在行为使用反斜杠'\': [kodango@devops awk_temp]$ cat test.awk function t(a) { b=a print "This is a very long line, so use backslash to escape the newline \ then we will print the variable a: a=" a } { print b; t("kodango.me"); print b;} [kodango@devops awk_temp]$ echo 1 | awk -f test.awk This is a very long line, so use backslash to escape the newline then we will print the variable a: a=kodango.me kodango.me 这里我们将脚本写到文件中,并通过-f参数来指定。但是,在一些特殊符号之后,是可以直接换行的,例如", { && ||"。

    模式(Pattern)

    模式是awk中比较重要的一部分,它有以下几种情况: /regular expression/: 扩展的正则表达式(Extended Regular Expression), 关于ERE可以参考这篇文章; relational expression: 关系表达式,例如大于、小于、等于,关系表达式结果为true表示匹配; BEGIN: 特殊的模式,在第一个记录处理之前被执行,常用于初始化语句的执行; END: 特殊的模式,在最后一个记录处理之前被执行,常用于输出汇总信息; pattern, pattern:模式对,匹配两者之间的所有记录,类似sed的地址对; 例如查找匹配数字3的行: [kodango@devops awk_temp]$ seq 1 20 | awk '/3/ {print}' 3 13 相反地,可以在在正则表达式之前加上'!'表示不匹配: [kodango@devops awk_temp]$ seq 1 5 | awk '!/3/ {print}' 1 2 4 5 除了 BEGIN和 END这两个特殊的模式外,其余的模式都可以使用'&&'或者'||'运算符组合,前者表示逻辑与,后者表示逻辑或: [kodango@devops awk_temp]$ seq 1 50 | awk '/3/ && /1/ {print}' 13 31 前面的正则都是整行匹配,有时候仅仅需要匹配某个字符,这样我们可以用表达式 $n ~ /ere/: [kodango@devops ~]$ awk '$1 ~ /ko/ {print}' /etc/passwd kodango:x:1000:1000::/home/kodango:/bin/bash 有时候我们只想显示特定和行,例如显示第一行: [kodango@devops ~]$ seq 1 5 | awk 'NR==1 {print}' 1

    正则表达式(Regular Expression)

    和 sed篇一样,这里我不会详细介绍正则表达式。因为正则表达式的内容介绍起来太麻烦,还是推荐同学阅读现有的文章(如 Linux/Unix工具与正则表达式的POSIX规范),里面对各个流派的正则表达式归纳地很清楚了。

    表达式(Expressions)

    表达式可以由常量、变量、运算符和函数组成,常数和变量的值可以为字符串和数值。 Awk中的变量有三种类型:用户定义的变量,内置变量和字段变量。其中,内置变量名都是大写的。 变量并不非一定要被声明或者被初始化 ,一个变量默认的值是空字符串,只是在某些上下文上会隐式的自动转换成数字0(例如数学运算),记住awk中的变量是无类型的,不存在字符串变量还是数字变量的区别,只是有时候为了解说方便,才会这么说。(感谢网友@ 紫云妃的 提醒) 字段变量可以用$n来引用,n的取值范围为[0,NF]。n可以为一个变量,例如$NF代码最后一个字段,而$(NF-1)表示倒数第二个字段。

    数组

    数组是一种特殊的变量,awk中的数组都是关联数组,它的下标都是字符串值(man手册中的原话是:All arrays in AWK are associative, i.e. indexed by string values),即使你使用的下标是一个数字,awk也会将下标隐式转换成字符串。所以容易给人一个误解,数组的下标可以是数字或者字符串。 数组的赋值很简单,下面将value赋值给数组下标为index的元素: array[index]=value 可以用for..in..语法遍历数组元素,其中item是数组元素对应的下标: for (item in array) 当然也可以在if分支判断中使用in操作符: if (item in array) 一个完整的例子如下所示: [kodango@devops ~]$ echo "1 2 3" | awk '{ for (i=0;i<NF;i++) a[i]=i; } END { print 3 in a for (i in a) printf "%s: %s\n", i, a[i]; }' 0 0: 0 1: 1 2: 2

    内置变量

    Awk在内部维护了许多内置变量,或者称为系统变量,例如之前提到的 FS、 RS等等。常见的内置变量如下表所示 变量名描述ARGC命令行参数的各个,即ARGV数组的长度ARGV存放命令行参数CONVFMT定义awk内部数值转换成字符串的格式,默认值为"%.6g"OFMT定义输出时数值转换成字符串的格式,默认值为"%.6g"ENVIRON存放系统环境变量的关联数组FILENAME当前被处理的文件名NR记录的总个数FNR当前文件中的记录的总个数FS字段分隔符,默认为空白NF每个记录中字段的个数RS记录的分隔符,默认为回车OFS输出时字段的分隔符,默认为空白ORS输出时记录的分隔符,默认为回车RLENGTH被match函数匹配的子串长度RSTART被match函数匹配的子串位于目标字符串的起始下标 下面主要介绍几个比较难理解的内置变量: 1. ARGV与ARGC ARGV与 ARGC的意思比较好理解,就像C语言 main(int argc, char **argv)。 ARGV数组的下标从0开始到 ARGC-1,它存放的是命令行参数,并且排除命令行选项(例如-v/-f)以及program部分。因此事实上 ARGV只是存储argument的部分,即文件名(file)以及命令行变量赋值两部分的内容。 通过下面的例子可以大概了解ARGC与ARGV的用法: [kodango@devops awk_temp]$ awk 'BEGIN { > for (i = 0; i < ARGC; i++) > print ARGV[i] > }' inventory-shipped BBS-list awk inventory-shipped BBS-list ARGV的用法不仅限于此,它是可以修改的,可以更改数组元素的值,可以增加数组元素或者删除数组元素。 a. 更改 ARGV元素的值 假设我们有a, b两个文件,它们各有一行内容:file a和file b。现在利用ARGV,我们可以做到偷梁换柱: [kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]="b"} {print}' a file b 这里要注意 ARGV[1]="b"的引号不能缺少,否则 ARGV[1]=b会将变量b的值赋值给 ARGV[1]。 当awk处理完一个文件之后,它会从 ARGV的下一个元素获取参数,如果是一个文件则继续处理,如果是一个变量赋值则执行赋值操作: [kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]="var=1"} {print var}' a b 1 为什么这里只打印一次变量值呢?可以回头再看看 上一篇中介绍变量赋值的内容。 而当下一个元素为空时,则跳过不处理,这样可以避开处理某个文件: [kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]=""} {print}' a b file b 上面的例子中a这个文件就被跳过了。 而当下一个元素的值为"-"时,表明从标准输入读取内容: [kodango@devops awk_temp]$ awk 'BEGIN{ARGV[1]="-"} {print}' a b a a # --> 这里按下CTRL+D停止输入 file b b. 删除 ARGV元素 删除 ARGV元素和将元素的值赋值为空的效果是一样的,它们都会跳转对某个参数的处理: [kodango@devops awk_temp]$ awk 'BEGIN{delete ARGV[1]} {print}' a b file b 删除数组元素可以用 delete语句。 c. 增加 ARGV元素 我第一次看到 ARGV变量的时候就在想,能不能利用 ARGV变量避免提供命令行参数,就像这样: awk 'BEGIN{ARGV[1]="a";} {print}' 但是事实上这样不行,awk会依然从标准输入中获取内容。下面的方法倒是可以,首先增加 ARGC的值,再增加 ARGV元素,我到现在也没搞懂这两者的区别: [kodango@devops awk_temp]$ awk 'BEGIN{ARGC+=1;ARGV[1]="a"} {print}' file a 2. CONVFMT与OFMT Awk中允许数值到字符串相互转换,其中内置变量 CONVFMT定义了awk内部数值到字符串转换的格式,它的默认值为"%.6g": [kodango@devops awk_temp]$ awk 'BEGIN { printf "CONVFMT=%s, num=%f, str=%s\n", CONVFMT, 12.11, 12.11 }' CONVFMT=%.6g, num=12.110000, str=12.11 通过更改 CONVFMT,我们可以定义自己的转换格式: [kodango@devops awk_temp]$ awk 'BEGIN { CONVFMT="%d"; printf "CONVFMT=%s, num=%f, str=%s\n", CONVFMT, 12.11, 12.11 }' CONVFMT=%d, num=12.110000, str=12 与此对应地还有一个内置变量 OFMT,它与 CONVFMT的作用是类似的,只不过是影响输出的时候数字转换成字符串的格式: [kodango@devops awk_temp]$ awk 'BEGIN { OFMT="%d";print 12.11 }' 12 3. ENVIRON ENVIRON是一个存放系统环境变量的关联数组,它的下标是环境变量名称,值是相应环境变量的值。例如: [kodango@devops awk_temp]$ awk 'BEGIN { print ENVIRON["USER"] }' kodango 利用环境变量也可以将值传递给awk: [kodango@devops awk_temp]$ U=hello awk 'BEGIN { print ENVIRON["U"] }' hello 可以利用for..in循环遍历 ENVIRON数组: [kodango@devops awk_temp]$ awk 'BEGIN { for (env in ENVIRON) printf "%s=%s\n", env, ENVIRON[env]; }' 4. RLENGTH与RSTART RLENGTH与 RSTART都是与 match函数相关的,前者表示匹配的子串长度,后者表示匹配的子串位于目标字符串的起始下标。例如: [kodango@devops ~]$ awk 'BEGIN {match("hello,world", /llo/); print RSTART,RLENGTH}' 3 3 关于 match函数,我们会在以后介绍。

    运算符

    表达式中必然少不了运算符,awk支持的运算符可以参见man手册中的“Expressions in awk”一小节内容: [kodango@devops awk_temp]$ man awk | grep "^ *Table: Expressions in" -A 42 | sed 's/^ *//' Table: Expressions in Decreasing Precedence in awk Syntax Name Type of Result Associativity ( expr ) Grouping Type of expr N/A $expr Field reference String N/A ++ lvalue Pre-increment Numeric N/A -- lvalue Pre-decrement Numeric N/A lvalue ++ Post-increment Numeric N/A lvalue -- Post-decrement Numeric N/A expr ^ expr Exponentiation Numeric Right ! expr Logical not Numeric N/A + expr Unary plus Numeric N/A - expr Unary minus Numeric N/A expr * expr Multiplication Numeric Left ...以下省略...

    语句(Statement)

    到目前为止,用得比较多的语句就是 print,其它的还有 printf、delete、break、continue、exit、next等等。这些语句与函数不同的是,它们不会使用带括号的参数,并且没有返回值。不过也有意外,比如 printf就可以像函数一样的调用: [kodango@devops awk_temp]$ echo 1 | awk '{printf("%s\n", "abc")}' abc break和 continue语句,大家应该比较了解,分别用于跳出循环和跳到下一个循环。 delete用于删除数组中的某个元素,这个我们在上面介绍 ARGV的时候也使用过。 exit的用法顾名思义,就是退出awk的处理,然后会执行 END部分的内容: [kodango@devops awk_temp]$ echo $'line1\nline2' | awk '{print;exit} END {print "exit.."}' line1 exit.. next语句类似sed的n命令,它会读取下一条记录,并重新回到脚本的最开始处执行: [kodango@devops awk_temp]$ echo $'line1\nline2' | awk '{ > print "Before next.." > print $0 > next > print "After next.." > }' Before next.. line1 Before next.. line2 从上面可以看出 next后面的print语句不会执行。 print与printf语句是使用最多的,它们将内容输出到标准输出。注意在print语句中,输出的变量之间带不带逗号是有区别的: [kodango@devops awk_temp]$ echo "1 2" | awk '{print $1, $2}' 1 2 [kodango@devops awk_temp]$ echo "1 2" | awk '{print $1 $2}' 12 print输出时,字段之间的分隔符可以由OFS重新定义: [kodango@devops awk_temp]$ echo "1 2" | awk '{OFS=";";print $1,$2}' 1;2 除此之外,print的输出还可以重定向到某个文件中或者某个命令: print items > output-file print items >> output-file print items | command 假设有这一样一个文件,第一列是语句名称,第二列是对应的说明: [kodango@devops awk_temp]$ cat column.txt statement|description delete|delete item from an array exit|exit from the awk process next|read next input record and process 现在我们要将两列的内容分别输出到statement.txt和description.txt两个文件中: [kodango@devops awk_temp]$ awk -F'|' '{ > print $1 > "statement.txt"; > print $2 > "description.txt" > }' column.txt [kodango@devops awk_temp]$ cat statement.txt statement delete exit next [kodango@devops awk_temp]$ cat description.txt description delete item from an array exit from the awk process read next input record and process 下面是一个重定向到命令的例子,假设我们要对下面的文件进行排序: [kodango@devops awk_temp]$ cat num.list 1 3 2 9 5 可以通过将print的内容重定向到"sort -n"命令: [kodango@devops awk_temp]$ awk '{print | "sort -n"}' num.list 1 2 3 5 9 printf命令的用法与print类似,也可以重定向到文件或者输出,只不过printf比print多了格式化字符串的功能。printf的语法也大多数语言包括bash的printf命令类似,这里就不多介绍了。 本篇文章主要介绍了awk语言中的一些基础元素概念, 下一篇的主题是awk编程语言中的常用函数。
    最新回复(0)