Linux文本处理三剑客之awk学习笔记07:语法

语法

变量

我觉得awk应该算是属于动态编程语言。其变量不需要事先声明才可以使用。我们想要使用的时候随时引用即可,不需要事先声明其数据类型。

awk的变量具有三种状态。

  • 未声明状态(untyped)。没有引用也没有赋值。
  • 未赋值状态(unassigned)。引用但还未赋值。
  • 已赋值状态。

引用未赋值的变量,其初始值为空字符串或者数字0。

从gnu awk 4.2.0版本开始提供了typeof()函数来判断一个变量的类型。

# awk 'BEGIN{print typeof(a)}'
untyped
# awk 'BEGIN{a;print typeof(a)}'
unassigned
# awk 'BEGIN{a=3;print typeof(a)}'
number
# awk 'BEGIN{a="alongdidi";print typeof(a)}'
string

对未引用且未赋值的数组进行判断,返回untyped。但是对未引用的数组中的未引用的元素进行判断却会返回unassigned,这个比较奇怪,需要记住。

# awk 'BEGIN{print typeof(arr)}'
untyped
# awk 'BEGIN{print typeof(arr["name"])}'
unassigned

如果是4.2.0之前的版本,可以使用如下方法判断变量状态。

awk 'BEGIN {
    if(a==""&&a==0) {
        print "Untyped or unassigned."
    } else {
        print "Assigned."
    }
}'    

变量赋值

在awk对变量进行赋值可以看作是一个有返回值的表达式(expression)。

# awk 'BEGIN{print a=3}'
3

它等价于:

awk 'BEGIN{a=3;print a}'

基于变量赋值可以返回值的特点,可以作连续的变量赋值。

x=y=z=5
# 等价于
z=5;y=5;x=5

可以将赋值语句放在允许使用表达式来评估值的情况。

# awk 'BEGIN{if(a=1){print "True"}else{print "False"}}'
True
# awk 'BEGIN{if(a=0){print "True"}else{print "False"}}'
False

如果代码逻辑比较复杂,不建议将变量赋值语句放入表达式。

# awk 'BEGIN{a=1;arr[a+=2]=a=a+6;print arr[9];print arr[3]}'
7
# 空

这里不好确定先计算等号(红色加粗)左边的数组索引赋值还是先计算等号右边的变量赋值,可能会根据不同的awk版本而不同。

变量使用

变量可以在三个位置进行赋值。

awk -v var=val [-v var=val ...] '{code}' var=val file1 var=val file2
  1. 在-v选项中赋值,如果赋值多个需要使用多个-v选项。
  2. 在代码块中赋值。
  3. 在文件前赋值。

不同位置赋值的变量的作用域会有所不同。例如在main代码块中赋值的变量在BEGIN中就无法引用,这点根据awk的工作流程即可得出。

在文件前进行变量赋值,在某些情况下适合于修改FS。

awk '{...}' FS=" " a.txt FS=":" /etc/passwd

awk还可以引用shell当中的变量。

# name="alongdidi"
# awk -v nameAwk=$name 'BEGIN{print nameAwk}'
alongdidi
# awk 'BEGIN{print nameAwk='\"$name\"'}'
alongdidi
# awk '{print nameAwk}' nameAwk=$name a.txt 
alongdidi
...

数据类型

awk有两种基本的数据类型:字符串和数值。从4.2.0开始,还支持正则表达式类型。

数据是什么类型不能只看字面意义,例如看到数字不代表它就是数值类型。而是要看数据所处的上下文:在字符串操作环境下转换成字符串类型,在数值操作环境下转换为数值类型。

转换分为隐式转换和显式转换。

隐式转换

1、对数据进行算术运算可以将其转换为数值类型。

对于可转换成数值的数据会正确转换成数值,例如:“123”、“123abc”和“   123abc”。对于无法正确转换成数值的数据则转换成数值0,例如:“abc123”。

# awk 'BEGIN{a="123";print a+0;print typeof(a+0)}'
123
number
# awk 'BEGIN{a="123abc";print a+0;print typeof(a+0)}'
123
number
# awk 'BEGIN{a="   123abc";print a+0;print typeof(a+0)}'
123
number
# awk 'BEGIN{a="abc123";print a+0;print typeof(a+0)}'
0
number

算术运算不止有加法,四则运算均可以。"string"+0比较常用,在不改变数值大小的情况下转换成数值。

2、对数据进行字符串连接操作可以将其转换成字符串类型。

使用空格和双引号连接。

# awk 'BEGIN{print typeof(123"")}'
string
# awk 'BEGIN{print typeof(123 123)}'
string

变量a和b是数值,使用空格连接后隐式转换成string,再使用加法运算后隐式转换成number。

# awk 'BEGIN{a=2;b=3;print a b}'
23
# awk 'BEGIN{a=2;b=3;print (a b)+4}'
27
# awk 'BEGIN{a=2;b=3;print typeof((a b)+4)}'
number

显式转换

1、使用函数sprintf()基于预定义变量CONVFMT将数值转换成字符串。

CONVFMT的默认值是%.6g。

# awk 'BEGIN{print CONVFMT}'
%.6g

变量a是小数,遇到""字符串连接时基于CONVFMT的值隐式转换成字符串"123.46",然后再由print基于OFMT转换后输出。

# awk 'BEGIN{a=123.4567;print a""}'
123.457
# awk 'BEGIN{a=123.4567;CONVFMT="%.2f";print a""}'
123.46

2、使用strtonum()函数显式地将字符串转换成数字。

# awk 'BEGIN{a="100";print strtonum(a);print typeof(strtonum(a))}'
100
number
# awk 'BEGIN{a="abc";print strtonum(a);print typeof(strtonum(a))}'
0
number

字面量

awk中有3种字面量,刚好对应于3种变量的数据类型。

  • 字符串字面量。
  • 数值字面量。
  • 正则表达式字面量。

字面量的含义就是表示其字面含义,不会再引用其他东西。

数值字面量

整数、浮点数、科学计数法所表示的数字均为数值字面量,但是千万不能使用双引号包裹,否则就是字符串字面量了。

123
123.00
1.23e+8
1.23e-06

awk内部总是以浮点数的形式保存数值。如果在输出的时候发现数值是一个整数,则会自动丢弃小数部分。

# awk 'BEGIN{a=10.0;print a}'
10

算术运算

以下算术运算符的优先级由高至低。

++ --        自增、自减。
^ **         幂运算(乘方)。
+ -          一元运算符,表示数字的正负性。
* / %        乘法、除法和取模。
+ -          二元运算符,加法和减法。

和其他编程语言一样,自增和自减运算,在变量出现的位置不同时有不同的效果,我们以自增运算为例进行阐述。

a++:先引用a的值参与运算,再对a进行自增。

++a:先对a进行自增,再引用a的值参与运算。

当自增运算独立作为语句存在时,二者没有区别。

# awk 'BEGIN{a=3;a++;print a}'
4
# awk 'BEGIN{a=3;++a;print a}'
4

当它们参与表达式的时候,情况则不同了。

# awk 'BEGIN{a=3;print a++;print a}'
3
4
# awk 'BEGIN{a=3;print ++a;print a}'
4
4

幂运算。^是幂运算的符号,这个是符合POSIX标准的,而**在有些版本的awk则无法使用,因此推荐仅使用^。

# awk 'BEGIN{print 2^3}'
8

幂运算的运算顺序是从右往左,而不是从左往右。因此下面这个示例中的值是512而不是64。

# awk 'BEGIN{print 2^3^2}'
512

赋值操作符的优先级是最低的,低于上述的算术运算符。

= += -= *= /= %= ^= **=

不建议书写一些容易引起歧义的语句。因为不同的awk版本可能有不同的结果。

# awk 'BEGIN{b=3;print b+=b++}'
7

字符串字面量

在awk中以双引号包裹的均为字符串字面量。不能以单引号包裹。

"alongdidi"
"29"
"\n"
" "
""

awk中没有为字符串的连接提供专门的操作符。只需要将字符串紧紧靠在一起或者使用空格(可多个)分隔即可。

# awk 'BEGIN{print "abc""def"}'
abcdef
# awk 'BEGIN{print "abc" "def"}'
abcdef
# awk 'BEGIN{print "abc"     "def"}'
abcdef

如果是字符串变量的话,则不能靠在一起,否则它们会组合成另一个变量。

# awk 'BEGIN{a="abc";b="def";print ab}'

字符串的串联也具备优先级概念,其(空格的)优先级低于加减运算。因此如下第一个示例中只有字符串的串联(两个数值通过空格字符串联成字符串),串联成功。

# awk 'BEGIN{print 12 " " 23}'
12 23
# awk 'BEGIN{print 12 " " -23}'
12-23
# 如果串联成功的话应该是这样的
12 -23

第二个示例,由于减法运算的优先级高所以先串联" "和-23,识别为二元减法运算等同于0-23等于数值-23,再将数值12和数值-23串联(因为中间有空格所以串联)。所以串联的结果是“12-23”而不是“12 -23”。

关于操作符的优先级,在下文会有详述。

正则表达式字面量

正则此前我们已经接触过了。形如这样的就是正则的字面量。

/Alice/
/A.*e/
/[[:alnum:]]+/

正则使用在pattern中。匹配方式如下。

"str"~/pattern/
"str"!~/pattern/

这里的字符串字面量"str"我们也可以使用变量来替代,常使用$0或者$N等。

如果/pattern/单独出现,则等同于$0~/pattern/。

正则匹配结果有返回值,匹配成功返回1,匹配失败返回0。

因此在使用正则是会有一些需要注意的点。

1、下面两个是等价的,a的值永远只会是0或者1,而不会保存正则字面量来用于后续的引用。下文会详述如何使用变量来保存正则字面量。

a=/pattern/
a=$0~/pattern/

2、下面三个是逐步等价的,不过一般也不会这么写(/pattern/~$1)就是了。需要明白这个过程,因为一般awk不会报错。

/pattern/~$1
$0~/pattern/~$1
0/1~$1

3、期望把正则字面量作为参数传递给函数。

a=/Alice/
func(arg1,a)

其实传递过去的a的值,只会是0或者1而已。

除了这3个以外还会有很多需要注意的点,主要都是源于/pattern/是$0~/pattern/的缩写以及不能直接将正则字面量赋值给变量来使用。

想要将正则字面量赋值给变量,必须使用4.2.0版本。从该版本开始变量的数据类型新增了正则类型。使用方式是在赋值时,在正则字面量前面加一个@。

# awk 'BEGIN{a=@/Alice/;print typeof(a)}' a.txt
regexp
# awk 'BEGIN{a=@/Alice/}$0~a{print}' a.txt
2   Alice   female  24   def@gmail.com  18084925203

当使用了正则类型的变量时,就不能简写正则匹配了。

a=@/pattern/
$0~a{action}    # 正确匹配
a{action}    # 错误匹配

因此这样子就无法只打印Alice所在行了。

awk 'BEGIN{a=@/Alice/}a{print}' a.txt

gawk支持的正则表达式

.:匹配任意单个字符,在gawk中包括了换行符。

^:匹配行首。

$:匹配行尾。

[...]:匹配中括号内的任意单个字符。

[^...]:匹配中括号外的任意单个字符。

|:逻辑或,二选一。

+:匹配前一个字符至少一次。

*:匹配前一个字符任意次(0次、1次或者多次)。

?:匹配前一个字符0次或者1次。

():分组捕获用于反向引用。

{m}:精确匹配前一个字符m次。

{m,}:匹配前一个字符至少m次。

{m,n}:匹配前一个字符m到n次。

{,n}:匹配前一个字符至多n次。

[:lower:]:小写字母。

[:upper:]:大写字母。

[:alpha:]:字母(大小写均可)。

[:digit:]:数字。

[:alnum:]:字母或者数字。

[:xdigit:]:十六进制字符。

[:blank:]:空格或者制表(TAB)字符。

[:space:]:空格字符,包含空格、TAB、换行、回车(carriage return)、进纸(form feed)和垂直(vertical)TAB。

[:punct:]:标点符号字符。非字母、数字、控制和空格字符。

[:graph:]:既可打印又可见的字符。比如字母是即可打印又可见,但是空格是可打印但是不可见的。

[:print:]:可打印的字符,即非控制字符。

[:cntrl:]:控制字符。

以下是gawk所支持的正则元字符。

\y:单词的起始或者结束部分的空字符,即单词的左边界或者右边界。\yballs?\y,可以匹配单词ball或者单词balls。

\B:单词内部字符之间的空字符。比如cr\Bea\Bte,可以匹配create不能匹配“crea te”。

\<:单词的左边界。

\>:单词的右边界。

\s:任意单个空白字符,等同于[[:space:]]。

\S:任意单个非空白字符,等同于[^[:space:]]。

\w:任意单个字母、数字或者下划线,等同于[[:alnum:]_],这三种字符也是单词的组成成分。

\W:等同于\w的取反[^[:alnum:]_]。

\`:绝对行首。例如当遇到“abc\ndef\nghi”,行首和行尾各有3个位置,而绝对行首只会是a的前面,绝对行尾只会是i的后面。

\':绝对行尾。

awk中不支持正则的修饰符,因此若想要忽略大小写进行匹配,就需要先将其统一转换成大/小写再来匹配;或者预设预定义变量IGNORECASE。

# awk '$2~/alice/{print}' a.txt 
# awk 'tolower($2)~/alice/{print}' a.txt 
2   Alice   female  24   def@gmail.com  18084925203
# awk 'BEGIN{IGNORECASE=1}$2~/alice/{print}' a.txt 
2   Alice   female  24   def@gmail.com  18084925203

awk布尔值

在awk中没有像其他编程语言那样提供true和false关键词来表示布尔值。不过它的布尔逻辑却十分简单:

  • 数值0表示布尔假。
  • 空字符串表示布尔假。
  • 其他情况均表示布尔真。注意,"0"表示布尔真,因为它是非空字符串而不是数值0。

上面我们说了,正则匹配有返回值,匹配成功返回1,匹配失败返回0。

布尔运算也有返回值,布尔真返回1,布尔假返回0。

# awk 'BEGIN{if(0){print "True"}}'
# awk 'BEGIN{if(""){print "True"}}'
# awk 'BEGIN{if("0"){print "True"}}'
True
# awk 'BEGIN{if("alongdidi"){print "True"}}'
True
# awk 'BEGIN{if(100){print "True"}}'
True
# awk 'BEGIN{if(a=100){print "True"}}'    # 赋值操作有返回值,此处返回数值100,布尔真。
True
# awk 'BEGIN{if(a==100){print "True"}}'
# awk 'BEGIN{if("alongdidi"~/a+/){print "True"}}'    # 正则匹配成功返回1,布尔真。
True

awk中比较操作

strnum类型

awk中数据的来源:

  1. 内部产生。包括变量的赋值、表达式或者函数的返回值等。
  2. 外部数据。来源于外部的数据如读取的文件,用户的输入等。

在不考虑gawk 4.2.0版本开始引入的正则表达式类型的情况下,awk基本的数据类型为字符串(string)和数值(number or numeric)。对于来自外部的数据(例如从文件中读取的数据),它们理论上应该都是字符串类型。但是有一些字符串类型的数据看起来又是数值的形式,例如a.txt中,从第2行开始的$1、$4和$NF。对于这种类型的数据有时候需要将它们视为数值而有的时候需要将它们视为字符串。

因此POSIX定义了一种叫做“numeric string”的类型来表示此类数据。在gawk中使用strnum数据类型来表示。当获取到的用户数据看起来像数值时,它是strnum类型,在使用的时候将其视为数值类型。

注意:数据类型strnum只针对于awk中除了数据常量、字符串常量和表达式计算结果以外的数据。例如从文件中读取的字段、数组中的元素等等。

虽然来源于管道的外部数据原本应该均被识别为string,但是由于某些看起来形似数值因此在某些情况下被识别为了strnum。

# echo "30" | awk '{print typeof($0)}'
strnum
# echo "+30" | awk '{print typeof($0)}'
strnum
# echo " 30" | awk '{print typeof($0)}'
strnum
# echo " +30" | awk '{print typeof($0)}'
strnum
# echo "30a" | awk '{print typeof($0)}'
string
# echo "a30" | awk '{print typeof($0)}'
string
# echo "30 a" | awk '{print typeof($0),typeof($1)}'
string strnum

大小比较操作

比较操作符。

<, >, <=, >=, !=, ==:大小、等值比较。
in:数组成员测试。

比较规则。

        +----------------------------------------------
        |       STRING          NUMERIC         STRNUM
--------+----------------------------------------------
STRING  |       string          string          string
NUMERIC |       string          numeric         numeric
STRNUM  |       string          numeric         numeric
--------+----------------------------------------------

简而言之,string的优先级最高,一旦操作符两边有一方是string,则双方采用string类型比较。否则其他情况下均采用numeric类型比较。

我们输出$0和$1以及它们对应的数据类型。虽然是外部数据理应均为string但是由于形似数值,因此均被识别为strnum。

# echo ' +3.14' | awk '{print "---"$0"---";print "---"$1"---"}' 
--- +3.14---
---+3.14---
# echo ' +3.14' | awk '{print typeof($0);print typeof($1)}'
strnum
strnum

第一组比较:在这组比较中,strnum和string进行比较,只要其中一方是string则双方采用string方式比较。字符串逐字符进行比较,因此第一对真,后两对假。

# echo ' +3.14' | awk '{print($0==" +3.14")}'
1
# echo ' +3.14' | awk '{print($0=="+3.14")}'
0
# echo ' +3.14' | awk '{print($0=="3.14")}'
0

第二组比较:在这组比较中,$0和$1均为strnum类型数据,差别只在一个空格字符,strnum和numeric进行比较按照numeric方式进行比较。因此$0和$1均按照数值3.14来比较,因此两对比较返回布尔真。

# echo ' +3.14' | awk '{print($0==3.14)}'
1
# echo ' +3.14' | awk '{print($1==3.14)}'
1

第三组比较:这组和第一组的比较原理其实相同,第一组看懂了,这里就懂了,因此就不做解释了。

# echo ' +3.14' | awk '{print($1==" +3.14")}'
0
# echo ' +3.14' | awk '{print($1=="+3.14")}'
1
# echo ' +3.14' | awk '{print($1=="3.14")}'
0

第四组比较:awk可以识别echo通过管道传递过来的科学计数表示法的数值1e2,并将其识别为strnum,然后进行strnum和strnum的比较,按照numeric进行比较,结果显而易见。对于三目运算符“?:”不懂的,后续也有解释。

# echo 1e2 3 | awk '{print $1;print $2;print typeof($1);print typeof($2)}'
1e2
3
strnum
strnum
# echo 1e2 3 | awk '{print ($1>$2)?"True":"False"}'
True

采用字符串比较时需要注意,它是按照(应该吧)ASCII编码表逐字符一一比较。

图形1的ASCII编码(十进制)是49,9是57,a是97。

# 以下均为true。
awk 'BEGIN{print("1"<9)}'
awk 'BEGIN{print(9<"a")}'
awk 'BEGIN{print(1<"a")}'
awk 'BEGIN{print(999<"a11")}'

逻辑运算

expr1 && expr2:逻辑与,二元运算符。如果expr1为假则无需计算expr2(即短路运算),直接判定整个逻辑与表达式为假。

expr1 || expr2:逻辑或,二元运算符。如果expr1为真则无需计算expr2(即短路运算),直接判定整个逻辑或表达式为真。

! expr:逻辑取反(非),一元运算符。

可以使用这个“!”或者“!!”将数据转换成布尔值0或者1。

awk 'BEGIN{print(!99)}'    # 0
awk 'BEGIN{print(!"ab")}'    # 0
awk 'BEGIN{print(!0)}'    # 1
awk 'BEGIN{print(!ab)}'    # 1
awk 'BEGIN{print(!"")}'    # 1

awk 'BEGIN{print(!!99)}'    # 1
awk 'BEGIN{print(!!"ab")}'    # 1
awk 'BEGIN{print(!!0)}'    # 0
awk 'BEGIN{print(!!ab)}'    # 0
awk 'BEGIN{print(!!"")}'    # 0

由于变量在初次使用且未赋值的情况下,其值为空字符串或者数值0,因此其表示的布尔值是假。将这个特性与取反操作相结合,我们可以实现对指定范围的数据进行处理。思路是:

  1. 设置符合条件的范围起始位置,使用var=!var使得var的布尔值为真。注意,这里的var可以是任意变量,只要是未赋值(或者空字符串或者数值0)即可。
  2. 将var作为pattern条件进行数据处理。
  3. 处理结束后在范围终止位置使用var=!var使得var的布尔值为假。

例如,打印a.txt的第1行至第4行数据。我们可以使用以往使用的方式实现。

awk 'NR<=4{print}' a.txt
awk 'NR>=1&&NR<=4{print}' a.txt

或者使用我们刚说的思路实现。

# awk 'NR==1{print;along=!along;next}along{print}NR==4{along=!along;next}' a.txt 
ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
2   Alice   female  24   def@gmail.com  18084925203
3   Tony    male    21   aaa@163.com    17048792503

也可以使用字符串连接存储到变量中后,在最后(END)再打印。

awk 'NR==1{print;along=!along;next}along{multiLine=multiLine$0"\n"}NR==4{along=!along;next}END{printf multiLine}' a.txt

运算符优先级

运算/操作符(operator)优先级由高至低排列。

()
$
++ --
+ - !
* / %
+ -
space
| |&
< > <= >= != ==
~ !~
in
&&
||
?:
= += -= *= /= %= ^=

()优先级最高;一元运算符的优先级高于大多数二元运算符;space是空格,表示字符串连接,在字符串字面量的字符串串联中我们已经解释过字符串串联优先级对于串联结果的影响了。

对于优先级相同的运算符,运算顺序是从左往右,不过对于赋值和幂运算来说是从右往左运算。

a-b+c  ==>  (a-b)+c
a=b=c  ==>  a=(b=c)
2**2**3  ==>  2**(2**3)

>除了是大于号,也表示输出重定向,一般来说它的运算符应保持最低才不会出现意外的结果。

awk 'BEGIN{print "foo">1<2?"true.txt":"false.txt"}'
awk 'BEGIN{print "foo">(1<2?"true.txt":"false.txt")}'

流程控制语句

这部分其实大多数的编程语言都是一样的,我的博客中的bash的学习笔记对于这些基本的东西也描述较多,因此对于重复的东西就不再赘述了。

在awk中,代码块{...}并不会分隔变量的作用域。例如在某个循环中我们已经使用了一个变量i,那么退出循环以后,这个变量i仍然有效。

# awk 'BEGIN{for(i=1;i<=10;i++){} print i}'
11

if语句

# 单分支
if(cond){
    statements
}

# 双分支
if(cond){
    statements1
}else{
    statements2
}

# 多分支
if(cond1){
    statements1
}else if(cond2){
    statements2
}else if(cond3){
    statements3
}else{
    statementsLast
}

我们来看一个有趣的示例。有一对夫妻,丈夫是一名程序员,妻子对丈夫说“出去买一个包子,如果看到卖西瓜的,就买两个。”

# 自然语言理解
买1个包子
if(看到卖西瓜的){
    买2个西瓜
}

# 编程语言理解
if(看到卖西瓜的){
    买2个包子
}else{
    买1个包子
}

示例。

# cat if.awk 
BEGIN{
    if(mark>=0&&mark<60) {
        print "bad"
    } else if(mark>=60&&mark<90) {
        print "ordinary"
    } else if(mark>=90&&mark<=100) {
        print "Good"
    } else {
        print "error mark"
    }
}
# awk -v mark=100 -f if.awk 
Good

条件表达式

条件表达式(Conditional Expression),也就是我们常见的三目运算符。

selector ? if-true-exp : if-false-exp

三者均是表达式,首先计算selector,若值为真(我们已经在讲解布尔值时解释过awk的真和假的概念了)则计算表达式if-true-exp,并将其返回值作为整个表达式的返回值,否则计算if-false-exp并将其值作为整个表达式的返回值。

来看两个成功的示例。

awk 'BEGIN{mark=60;grade=mark>=60?"pass":"fail";print grade}'
awk 'BEGIN{mark=60;mark>=60?grade="pass":grade="fail";print grade}'

要注意这三者均是表达式,它们和普通语句的不同点在于它们可以计算评估并拥有返回值,而普通的语句是不行的。例如。

# awk 'BEGIN{mark=60;mark>=60?print "pass":print "fail"}'
awk: cmd. line:1: BEGIN{mark=60;mark>=60?print "pass":print "fail"}
awk: cmd. line:1:                        ^ syntax error

switch语句

awk的switch语句的功能和bash中的是一样的。区别在于awk的switch语句的每个分支需要显式使用break才可离开分支,否则会发生分支的穿透。

switch(expression) {
    case val1|regex1 : statements1
    case val2|regex2 : statements2
    case val3|regex3 : statements3
... ...
    [default: statemtsLast]
}

示例如下。除了每个分支的break以外,其余和我们在bash中所见到的switch并无二致。

# cat switch1.awk 
{
    switch($0){
        case 1:
            print "Monday."
            break
        case 2:
            print "Tuesday."
            break
        case 3:
            print "Wednesday."
            break
        case 4:
            print "Thursday."
            break
        case 5:
            print "Friday."
            break
        case 6:
            print "Saturday."
            break
        case 7:
            print "Sunday."
            break
        default:
            print "What day is today?"
    }
}
# awk -f switch1.awk 
0
What day is today?
8
What day is today?
1
Monday.
2
Tuesday.

What day is today?    # Ctrl+d结束输入

我们可以注释掉case=1|2|3|4分支的break指令来查看效果就可以理解什么是分支穿透,这个很简单就不演示了。

如果我们期望根据用户的输入来输出工作日或者周末信息,可以注释掉break。

{
    switch($0){
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
            print "Weekday."
            break
        case 6:
        case 7:
            print "Weekend."
            break
        default:
            print "What day of today?"
            break
     }
}

case的值不支持逻辑或,会报错。

# cat switch4.awk
{
    switch($0){
        case 1|2|3|4|5:
            print "Weekday."
            break
        case 6|7:
            print "Weekend."
            break
        default:
            print "What day of today?"
            break
     }
}
# awk -f switch4.awk 
awk: switch4.awk:3:         case 1|2|3|4|5:
awk: switch4.awk:3:               ^ syntax error

像这种情况就只能用正则了。

{
    switch($0){
        case /[12345]/:
            print "Weekday."
            break
        case /[67]/:
            print "Weekend."
            break
        default:
            print "What day is today?"
            break
    }
}

循环

while循环。

while(condition) {
    statements
}

do while循环。

do {
    statements
} while(condition)

for循环。

for(expr1;expr2;expr3) {
    statements
}

for循环遍历数组。

for(idx in arrary) {
    statements
}

break和continue

awk中的break和continue与bash中的几乎相同,区别仅在于awk中的break还用于switch...case...语句中的退出分支。

# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break}print i}}'
1
2
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue}print i}}'
1
2
4
5

next和nextfile

讲到next就会和getline一同解释。我们直接看命令执行结果。

# seq 1 5 | awk 'NR==3{next}{print}'
1
2
4
5
# seq 1 5 | awk 'NR==3{getline}{print}'
1
2
4
5

命令的执行结果相同,但是执行的过程是不同的。

代码块中的pattern和{action}一同构成一条规则(rule),在这个示例中有2条规则,第一条规则是pattern为NR==3的,第二条规则则没有pattern,意味着每一条记录都符合该规则。

next:读取下一行,然后重新回到规则的头部(NR==3的位置)处理。

getline:读取下一行,然后在当前位置(getline的位置)继续往下处理。

也就是说第一条命令next之后重新回到规则头部判断NR是否等于3了,而第二条命令在getline之后执行的是无pattern的print。

nextfile:next命令是停止当前正在处理的记录而后进入下一条记录,而nextfile命令则是停止当前正在处理的文件而后进入下一个文件的处理。

# awk 'NR==3{nextfile}{print}' a.txt a.txt 
ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
ID  name    gender  age  email          phone
... ...
10  Bruce   female  27   bcbd@139.com   13942943905
# awk 'FNR==3{nextfile}{print}' a.txt a.txt 
ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012

exit

exit [code]

exit用于退出awk程序,可以带返回值。

如果在BEGIN或者main代码块中执行exit,则会停止当前处理而后执行END代码块(若有的话)的内容。也就是说exit命令的执行包含了执行END代码块。

如果在END代码快中执行exit,则程序直接退出。

我们在讲解BEGIN和END代码块时曾说过,若存在END代码块但是没有遇到EOF(遇到文件的结尾或者STDIN中键入Ctrl+d),则END代码块阻塞不执行。但是现在有exit的执行,即使没有EOF我们也可以执行END代码块的内容了。

# awk 'BEGIN{print "Hello world!";exit}END{print "This is not end!!!"}'    # 这里既没有文件的处理,下面2行也不是STDIN。
Hello world!
This is not end!!!

如果没有BEGIN,则至少需要在main中做一次输入。

# awk '{print "Hello world!";exit}END{print "This is not end!!!"}'
1    # 用户输入
Hello world!
This is not end!!!

有时候为了让BEGIN或者main中的exit真的像其他编程语言(如bash)那样直接退出程序,我们可以在exit前设置一个变量(如flag等),然后在END的头部判断该变量来决定是否退出。

# awk 'BEGIN{print "Hello world!";flag=1;exit}{}END{if(flag){exit};print "end code"}'
Hello world!

较完整的伪代码如下:

BEGIN {
    ... ...
    if(begin cond) {
        flag=1
        exit
    }
    ...
}
{
    ... ...
    if(main cond) {
        flag=1
        exit
    }
    ... ...
}
END {
    if(flag){exit}    # 必须在END头部。
    ... ...
}

exit可以指定退出状态码(返回值),如果带状态码的exit只有一次,那么采用该状态码。

# awk 'BEGIN{exit 100}'
# echo $?
100
# awk '{exit 100}'    # 这里必须至少一次输入,如果直接Ctrl+d,返回的是0而不是exit的状态码。
1
# echo $?
100
# awk '{exit 100}'    # 直接Ctrl+d的结果。
# echo $?
0
# awk 'END{exit 100}'    # 这里也是直接Ctrl+d,结果不同是因为代码块不同。
# echo $?
100

如果有多个exit并且不止一个exit有状态码的话,则退出状态码是最后一次有执行的的带状态码的exit的状态码。

# awk 'BEGIN{exit 10}{exit 20}END{exit 30}'
# echo $?
30
# awk '{exit 20}END{exit 30}'
# echo $?
30
# awk '{exit 20}END{exit 30}'
1
# echo $?
30
# awk 'BEGIN{exit 10}{exit 20}END{exit}'
# echo $?
10    # 最后的exit没有状态码,最后一次有状态码的exit是BEGIN中的,main中的不会执行。

 

posted @ 2021-01-20 22:40  阿龙弟弟  阅读(639)  评论(0编辑  收藏  举报
回到顶部