玩转gawk


awk '代码块' test.txt

awk读取文件,可以是0个文件或一个文件,也可以多个文件

如果不给定任何文件,但又需要读取文件,则表示从标准输入中读取

单引号包围的是awk代码,也称为awk程序

  • 尽量使用单引号,因为在awk中经常使用$ 符号,而$符号在Shell是变量符号,如果使用双引号包围awk代码,则$符号会被Shell解析成Shell变量,然后进行Shell变量替换。使用单引号包围awk代码,则 $ 会脱离Shell,使得$符号留给了awk去解析

  • awk程序中,大量使用大括号,大括号表示代码块,代码块中间可以之间连用,代码块内部的多个语句需使用分号";"分隔

    例如:

awk '{print $0}' test.txt
awk '{print $0}{print $0;print $0}' test.txt

BEGIN和END语句块

awk 'BEGIN{print "我在前面"}{print $0}' test.txt
awk 'END{print "我在后面"}{print $0}' test.txt
awk 'BEGIN{print "我在前面"}{print $0}END{print "我在后面"}' test.txt

BEGIN代码块:

在读取文件之前执行,且执行一次

在BEGIN代码块中,无法使用 $0 或其它一些特殊变量

END代码块:

在读取文件完成之后执行,且执行一次

有END代码块,必有要读取的数据(可以是标准输入)

END代码块中可以使用$0等一些特殊变量,只不过这些特殊变量保存的是最后一轮awk循环的数据

main代码块:

读取文件时循环执行,(默认情况)每读取一行,就执行一次main代码块

main代码块可有多个

awk命令行结构和语法结构


awk [ -- ] program-text file ...
awk -f program-file [ -- ] file ...
awk -e program-text [ -- ] file ...
command -x -r root -ppassword test.txt b.txt c.txt

# 双短横线 -- 表示选项到此结束,后面的都是命令的参数。
# 1.选项分为长选项和短选项  
# 2.选项分为3种:  
#   (1).不带参数的选项
#   (2).是带参数的选项,如果该选项后面没有给参数,则报错
#   (3).参数可选的选项,选项后面可以跟参数,也可以不跟参数
#       参数可选选项,如果要接参数,则必须将参数紧紧跟在选项后面,不能使用空格分隔选项和参数
# 3.两种参数:
#   (1).选项型参数
#   (2).非选项型参数

awk的语法充斥着 pattern{action} 模式{动作}的模式

awk 'BEGIN{n=3} /^[0-9]/{$1>5{$1=333;print $1} /Alice/{print "Alice"} END{print "hello"}' test.txt

pattern部分用于测试筛选数据,action表示在测试通过后执行的操作

pattern和action都可以省略

  • 省略 pattern ,等价于对每一行数据都执行action

    • awk '{print $0}' test.txt
      
  • 省略代码块 {action} ,等价于 {print} 即输出所有行

    • awk '/Alice/' test.txt 等价于 awk '/Alice/{print $0}' test.txt
      
  • 省略代码块中的 action ,表示对筛选的行什么都不做

    • awk '/Alice/{}' test.txt
      
  • pattern{action} 任何一部分都可以省略

    • awk '' test.txt
      
    • 多个 pattern{action} 可以直接连接连用

    • action中多个语句连用需使用分号分隔

关于pattern和action


对于 pattern{action} 语句结构(都称之为语句块),其中的pattern部分可以使用下面列出的模式:

# 特殊pattern
BEGIN
END

/regular expression/  		 # 正则匹配成功与否 /a.*ef/{action}
relational expression  		 # 即等值比较、大小比较 3>2{action}
pattern && pattern    		 # 逻辑与 3>2 && 3>1 {action}
pattern || pattern    		 # 逻辑或 3>2 || 3<1 {action}
! pattern             		 # 逻辑取反 !/a.*ef/{action}
(pattern)             		 # 改变优先级
pattern ? pattern : pattern  # 三目运算符决定的布尔值
pattern1, pattern2     		 # 范围匹配

action部分,可以是任何语句,例如print语句。

awk工作流程


BEGIN.main.END执行步骤

例:

[root@vm-nfs-41 ~]# awk -v key1="var1-v" '
BEGIN{key2="var2-begin"; print key1,key2,key3, "---BEGIN code ---"} \
 	 {print  key1,key2,key3,key4} \
 	 END{key4="var4-end"; print  key1,key2,key3,key4, "---END code ---"}'\
 	 key3="var3" /tmp/awk/test.txt 
 
var1-v var2-begin  ---BEGIN code ---
var1-v var2-begin var3 
var1-v var2-begin var3 
var1-v var2-begin var3 
var1-v var2-begin var3 
var1-v var2-begin var3 var4-end ---END code ---
  • 解析 -v key=var… 选项中的变量赋值
  • 编译awk源代码为awk可解释的内部格式,包括-v的变量
  • 执行BEGIN代码段(注意在BEGIN代码块中,无法使用 $0或其它一些特殊变量)
  • 根据输入记录分隔符RS读取文件(根据ARGV数组的元素决定要读取的文件),如果没有指定文件,则从标准输入中读取文件,同时执行main代码段
  • 如果文件名部分指定为 key=val格式,则声明并创建变量,此阶段的变量在BEGIN之后声明,所以BEGIN中不可用,main代码段和END代码段可用
  • 每读取一条记录:
    • 都将设置NR、FNR、RT、$0等变量,
    • (默认)根据输入字段分隔符FS切割字段,将各字段保存到 $1、$2… 中
    • 测试main代码段的pattern部分,如果测试成功则执行action部分
  • 执行END代码段
  • PS:记住,字符串一定要使用双引号

变量


awk的变量是动态变量,在使用时声明。

  • 所以awk变量有3种状态:
    • 未声明状态:称为untyped类型
    • 引用过但未赋值状态:unassigned类型 比如 :awk
    • 已赋值状态

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

在awk中未声明的变量称为untyped,声明了但未赋值(只要引用就代表已经声明)的变量其类型为unassigned。

基于GAWK4.2.0之后版本有typeof()函数可以检测变量类型

例如:

awk 'BEGIN{print typeof(var)}'
untyped  #未声明状态:称为untyped类型

awk 'BEGIN{print var,typeof(var)}'
 unassigned #变量var未赋值状为空,但是引用过

awk 'BEGIN{var=3;print var,typeof(var)}'
3 number #已赋值状态
awk 'BEGIN{var="3";print var,typeof(var)}'
3 string #已赋值状态

变量赋值


awk中的变量赋值语句也可以看作是一个有返回值的表达式。

例如,key="var"赋值完成后返回var,同时变量key也被设置为var。

基于这个特点,有两点用法:

  • 可以 a=b=c="var ,等价于 a="var"b=“var” c=“var”

    #首先会将key赋值给c,然后再将c的返回值'key'赋值给b........最后赋值给
    awk 'BEGIN{a=b=c="var";print a;print b;print c}'
    var
    var
    var
    
  • 可以将赋值语句放在任意允许使用表达式的地方

    awk 'BEGIN{print ((var=3) != (var=3))}'
    0 #false 返回值为0 
    
    awk 'BEGIN{print ((var=3) == (var=3))}'
    1 #true 返回值为1
    
    #if判断方式
    awk 'BEGIN{if((var=3) == (var=3)){print "true"}else{print "fasle"}}'
    true
    awk 'BEGIN{if((var=4) == (var=3)){print "true"}else{print "fasle"}}'
    fasle
    
    #需要注意的是(var=3) == (var=3)要使用()包裹,否则有可能产生歧义
    

变量赋值方式


  • 在BEGIN或main或END代码段中直接引用或赋值

  • 使用 -v key=var 选项,可定义多个,必须放在awk代码的前面

    • 它的变量声明早于BEGIN块

    • #普通变量
      awk -v key="hello" 'BEGIN{print key}'
      hello
      awk -v key="hello" 'BEGIN{key="world";print key}'
      world #BEGIN代码段中再次声明会覆盖-v 定义变量
      
  • 在awk代码后面使用 key=var 参数

    • 它的变量声明在BEGIN之后

    • awk 'BEGIN{key="world";print key}' key=hello
      world
       awk -v key="world" 'BEGIN{print key}' key=hello
      world
      #FS 预定义变量可根据文件进行单独设置  例如:
       awk 'BEGIN{ORS="---"}{print $1}' FS=":" /etc/passwd.txt FS=" " test.txt
      mysql
      apache
      zabbix
      dockerroot
      memcached
      kaixin
      springboot
      ID
      1
      2
      3
      4
      
  • 引用Shell变量

    • [root@sea awk]#: var="hello world"
      [root@sea awk]#: awk -v var="$var" 'BEGIN{print var}'
       hello world
      [root@sea awk]#: awk -v var=$var 'BEGIN{print var}'
      awk: 致命错误:无法以读模式打开文件“BEGIN{print var}”(没有那个文件或目录)
      #注意变量区别 如果shell 赋值变量为多个字段组成,awk中需要用双引号引起$var
      
      [root@sea awk]#: var=HELLO
      [root@sea awk]#: awk -v var=$var 'BEGIN{print var}'
       HELLO
      
    • [root@sea awk]#: var="HELLO WORLD"
      [root@sea awk]#: awk '{print var}' var=$var test.txt
      awk: 致命错误:无法以读模式打开文件“WORLD”(没有那个文件或目录)
      #注意变量区别 如果shell 赋值变量为多个字段组成,awk中需要用双引号引起$var
      
      [root@sea awk]#: awk '{print var}' var="$var" test.txt
      HELLO WORLD
      HELLO WORLD
      HELLO WORLD
      

数据类型


gawk有两种基本的数据类型:数值和字符串。在gawk 4.2.0版本中,还支持第三种基本的数据类型:正则表达式类型。

数据是什么类型在使用它的上下文中决定:在字符串操作环境下将转换为字符串,在数值操作环境下将转换为数值

隐式转换

  • ​ 算术加0操作可转换为数值类型

    • ​ “123” + 0 返回数值123

    • ​ " 123abc" + 0 转换为数值时为123

    • [root@sea awk]#: awk 'BEGIN{a="123";print a+0}'
      123
      [root@sea awk]#: awk 'BEGIN{a="123hello";print a+0}'
      123
      
      #typeof函数来验证
      [root@sea awk]#:  awk 'BEGIN{var="123hello"; print var,typeof(var);print var+3,typeof(var+3)}'
      123hello string
      126 	 number
      
  • 无效字符串将转换成0,例如 “abc”+3 返回3

    • [root@sea awk]#: awk 'BEGIN{a="b123he";print a+0}'
      0
      [root@sea awk]#: awk 'BEGIN{a="b123";print a+0}'
      0
      #只要不是以数字开头的都默认为无效字符串
      
  • ​ 连接空字符串可转换为字符串类型

    • ​ 123"" 转换为字符串"123

      [root@sea awk]#: awk 'BEGIN{var=123""; print var,typeof(var)}'
      123 string
      

显式转换strtonum

字符串—>数值

[root@sea awk]#: awk 'BEGIN{var="12345";print strtonum(var),typeof(strtonum(var)) }'
12345 number

#strtonum函数会根据OFMT格式输出所定义的浮点数位数进行转换,默认为最大6位
[root@sea awk]#: awk 'BEGIN{var="12.765421";print strtonum(var),typeof(strtonum(var)) }'
12.7654 number

数值–>字符串

CONVFMT或sprintf():功能等价。都是指定数值转换为字符串时的格式

[root@sea awk]#: awk 'BEGIN{var=123.3455;print var,typeof(var);CONVFMT="%.2f";print var"",typeof(var"") }'
123.346 number
123.35 string
#等价于
[root@sea awk]#: awk 'BEGIN{var=123.3455;print var,typeof(var);print sprintf ("%.2f", var),typeof(sprintf ("%.2f", var))}'
123.346 number
123.35 string

awk字面量与算数运算符


  • awk中有3种字面量:字符串字面量、数值字面量和正则表达式字面量。
    • 字符串字面量
      • 例如:a=“123”, print “123” 其中123就是字符串字面量
    • 数字字面量
      • 例如: a=123, print 123 其中132就是数值字面量
数值字面量
  • 整数、浮点数、科学计数
    • 105、105.0、1.05e+2、1050e-1
  • awk内部总是使用浮点数方式保存所有数值,但用户在使用可以转换成整数的数值时总会去掉小数点
    • 数值12.0面向用户的值为12,12面向awk内部的值是12.0000000…0(一堆小数位)
算数运算符

++ – 自增、自减,支持ii或–i或i–

^ 幂运算(**也用于幂运算)

+ - 一元运算符(正负数符号)

* / % 乘除取模运算

+ - 加减法运算

+= -= 赋值运算

#注意 优先级从上到下 +±-最高 +=最低

  1. ++和–既可以当作独立语句,也可以作为表达式,但是要注意++i(--i) 和i++(i--)的区别如:
[root@sea awk]#: awk 'BEGIN{i=6; print i++,"----",i }'  # 先返回i的值后在进行运算
6 ---- 7
[root@sea awk]#: awk 'BEGIN{i=6; print ++i,"----",i }'  # 先运算后在返回i的值
7 ---- 7
[root@sea awk]#: awk 'BEGIN{i=6;I=i++;print i,"----",I }'  #先将i赋值给I,再运算+1
7 ---- 6
[root@sea awk]#: awk 'BEGIN{i=6;I=++i;print i,"----",I }'  #先运算+1后,再将i赋值I
7 ---- 7
  1. ^幂运算是从右向左计算的,例如:
[root@sea awk]#: awk 'BEGIN{print 2^3^2}'
512
#会先计算3的2次方(9) 再运算2的9次方 为512
  1. 赋值操作(优先级最低):

    =   +=   -=   *=   /=   %=    ^=   **=
    
    awk 'BEGIN{i=6;i+=4;print i }'  # 等价于 awk 'BEGIN{i=6;i=i+4;print i }'
    10
    awk 'BEGIN{i=2;i^=3;print i }'  # 等价于 awk 'BEGIN{i=2;i=i^3;print i }'
    8
    
字符串字面量
  • awk中的字符串都以双引号包围,不能以单引号包围。
    • “abc”
    • “”
    • “\0” 、 “\n”

字符串连接(串联):awk没有为字符串的串联操作提供运算符,可以直接连接或使用空格连接。

例如:

 awk 'BEGIN{print "hello" "world"}' # 等价于 awk 'BEGIN{print "helloworld"}' 
helloworld
 awk 'BEGIN{var="hello";var1="world";print (var var1)}' 
helloworld 
 awk 'BEGIN{var="hello";var1="world";print var var1}'
helloworld

正则表达式字面量

普通正则:

  • /[0-9]+/

  • 匹配方式: “str” ~ /pattern/ 或 “str” !~ /pattern/

  • 匹配结果返回值为0(匹配失败)或1(匹配成功)

    • echo  "hello HELLO world WORLD"|awk 'BEGIN{RS=" "}{print /hello/}'
      1  #匹配成功
      0  #匹配失败
      0  #匹配失败
      0  #匹配失败
      
  • 任何单独出现的 /pattern/ 都等价于 $0 ~ /pattern/

    • if(/pattern/) 等价于 if($0 ~ /pattern/)

    • 坑1: a=/pattern/ 等价于将 $0 ~ /pattern/ 的匹配返回值(0或1)赋值给a

      • echo  "hello HELLO world WORLD"|awk 'BEGIN{RS=" "}a=/hello/{print a}'
        1
        
    • 坑2: /pattern/ 作为参数传给函数时,传递的是 $0~/pat/ 的结果0或1

    • 更多坑等待你去发掘…

强类型的正则字面量(gawk 4.2.0)

  • @/pattern/ 作为独立的一种数据类型:正则表达式类型

  • 在使用正则字面量变量进行匹配的时候,不能简写 var=@/world/;var{print} ,只能写完整的匹配var=@/world/;$0 ~ var{print}\

    • echo  "hello HELLO world WORLD"|awk 'BEGIN{RS=" ";var=@/world/} $0~var{print}'
      world
      
  • 可使用 typeof() 4.2后版本支持检查类型,得到的结果将是 regexp

    • awk 'BEGIN{re=@/hello/;print typeof(re)}'
      regexp
      

正则表达式

正则符号 解释 备注
. 匹配任意字符 gawk中可匹配换行符
^ 锚定开头
$ 锚定结尾
[a-z] 匹配a-z任意一个字母
[^…] 取反
| 二选一(或者)
+ 匹配前一个字符最少1次
* 匹配前一个字符0次或者多次
? 匹配前一个字符0次或者1次
() 分组捕获
前一个字符精确匹配m次
前一个字符最少m次
前一个字符最多m次
前一个字符最少m次,最多n次
前一个字符最多n次
[:lower:] 匹配小写字母 相当于[a-z]
[:upper:] 匹配大写字母 相当于[A-Z]
[:alpha:] 匹配所有字母 相当于[a-zA-Z]
[:digit:] 匹配数值 相当于[0-9]
[:alnum:] 匹配所有字母加数值
[:xdigit:] 16进制的数值
[:blank:] 匹配空格和制表符
[:space:] 匹配空白符匹配空白符(空格, 制表符,换行符,换页符等)
[:punct:] 匹配标点符号
[:graph:] 匹配任何一个看得见的可打印的字符
[:print:] 匹配任何一个可打印的字符
[:cntrl:] 匹配任何一个控制字符(ASCII码前32个字符)
\y 匹配单词左右边界部分的空字符位置 只有gawk支持
\B 匹配单词非单词的边界,例如hello\B 可匹配helloworld 不可匹配hello单独的单词 只有gawk支持
\< 匹配单词左边界
\> 匹配单词右边界
\s 匹配空白字符
\S 匹配非空白字符
\w 匹配单词组成字符(大小写字母、数字、下划线)
\W 匹配非单词组成字符
\` 匹配字符串的绝对行首 “abc\ndef” 只有gawk支持
\’ 匹配字符串的绝对行尾 只有gawk支持

注意:gawk不支持正则修饰符,所以无法直接指定忽略大小写的匹配。

如果想要实现忽略大小写匹配,则可以将字符串先转换为大写、小写再进行匹配。或者设置预定义变量IGNORECASE

为非0值。例如:

#设置IGNORECASE为非0值 忽略大小写进行匹配
echo  "hello HELLO world WORLD helloworld"|awk 'BEGIN{RS=" ";IGNORECASE=1}/hello$/{print}'
hello
HELLO

#也可以使用函数先转换为大写或者小写后在进行匹配,例如:
echo  "hello HELLO world WORLD helloworld"|awk 'BEGIN{RS=" "} toupper($0)~/HELLO$/{print}'
hello
HELLO
echo  "hello HELLO world WORLD helloworld"|awk 'BEGIN{RS=" "} tolower($0)~/hello$/{print}'
hello
HELLO

awk布尔值与逻辑运算

在awk中,没有像其它语言一样专门提供true、false这样的关键字。

但它的布尔值逻辑非常简单:

  • 数值0表示布尔假,但是字符串0为布尔真

    • awk 'BEGIN{ a=0; if(a){print a,"true"}else{print a,"false"}}'
      0 false
      
  • 空字符串表示布尔假

    • awk 'BEGIN{a="" ;if(a){print "true"}else {print "false"}}'
      false
      
  • 其余所有均为布尔真

    • awk 'BEGIN{a="2" ;if(a){print "true"}else {print "false"}}'
      true
      awk 'BEGIN{a=".;" ;if(a){print "true"}else {print "false"}}'
      true
      
      
    • 字符串"0"也是真,因为它是字符串

      • awk 'BEGIN{a="0" ;if(a){print "true"}else {print "false"}}'
        true	
        
  • awk中,正则匹配也有返回值,匹配成功则返回1,匹配失败则返回0

    • echo  "hello HELLO"|awk 'BEGIN{RS=" "}{print /hello/}'
      1  #匹配成功
      0  #匹配失败
      
      shell_var="hello world"
      awk -v awk_var="$shell_var" 'BEGIN{var=(awk_var ~ /hel.*o/);if(var){print var,"true"}else{print var,"false"}}'
      1 true
      
      shell_var=world
      awk -v awk_var="$shell_var" 'BEGIN{var=(awk_var ~ /hel.*o/);if(var){print var,"true"}else{print var,"false"}}'
      0 false
      
      
  • awk中,所有的布尔运算也有返回值,布尔真返回值1,布尔假返回值为0

    • awk 'BEGIN{var=(2>1);if(var){print var,"true"}else{print var,"false"} }'
      1 true  #返回值为1,布尔真
      awk 'BEGIN{var=(2<1);if(var){print var,"true"}else{print var,"false"} }'
      0 false #返回值为0,布尔假
      

strnum类型

​ awk最基本的数据类型只有string和number(gawk 4.2.0版本之后支持正则表达式类型)。但是,对于用户输入数据(例如从文件中读取的各个字段值),它们理应属于string类型,但有时候它们看上去可能像是数值(例如$1=6 ),而有时候有需要这些值是数值类型。

  • awk的数据主要来源:
    • awk内部产生的(变量、表达式或函数的返回值)。
    • 从其它来源获取到的数据,都是外部数据,也是用户输入数据,这些数据理应全部都是string类型的数据。

所以定义了一个名为"numeric string"的"墙头草"类型,awk中叫做strnum类型。当获取到的数据看上去是数字时,那么它就是strnum类型。strnum类型在被使用时会被当作数值类型。

例如:

cat test.txt

ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest     female  24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
4   user      male    21   www@189.com    18234532345
5   Alex      male    13   fff@xyz.com    14646324234

#其中ID age tel列就是strnum 在处理的时间会当做字符类型

awk '{print $1,typeof($1),"----",$2,typeof($2),"----",$4,typeof($4)}' test.txt
ID string ---- name string  ---- age string
1  strnum ---- admin string ---- 21  strnum
2  strnum ---- guest string ---- 24  strnum
3  strnum ---- Root string  ---- 21  strnum
4  strnum ---- user string  ---- 21  strnum
5  strnum ---- Alex string  ---- 13  strnum

大小比较操作

#比较操作符:
<   >    <=   >=   !=   == #大小、等值比较

比较规则:

string优先级最高,只要string类型参与比较,就要按照string的方式进行比较,将非string类型的进行隐式转换,要注意的是采用字符串比较时需注意,它是逐字符逐字符比较的。

其它时候都采用num类型比较。例如 当strnum类型与num类型比较时 strnum就是数值类型

例如:

#包含字符串的比较
awk 'BEGIN{a="100";b=2;if(a<b){print "true"}else{print "false"}}'
true

#数值比较
awk 'BEGIN{a=100;b=2;if(a<b){print "true"}else{print "false"}}'
false

逻辑运算

逻辑运算符 解释 备注
&& 逻辑于 expr1 && expr2 需要两个expr都为真,结果才为真
|| 逻辑或 expr1 || expr2 只有有一个expr为真,结果就为真
! 逻辑取反 逻辑取反的优先级高于逻辑与和逻辑或

! 可以将数据转换成数值的1或0(返回值),当然这个要取决于你的数据是布尔真还是布尔假。 例如比较大小11>9 ,并使用!取反

awk 'BEGIN{ a=(11>9); if(!a){print "true"}else{print "false"}}'
false

!! 可将数据转换成等价布尔值的1或0

实际就是取反再取反 例如比较大小11>9 ,并使用!取反再取反 例如:

[root@sea ~]#: awk 'BEGIN{ a=(11>9); if(!!a){print a,"true"}else{print a,"false"}}'
1 true

awk中变量未赋值时默认初始化为空字符串或数值0,也就是布尔假。是可以直接对一个未赋值的变量执行 ! 取反操作。

使用!可以完成一个awk的小技巧 就是利用!取反操作进行奇偶行打印 例如:

 awk 'i=!i' test.txt #第一次赋值i为空,即布尔假,然后取反后为布尔真,打印第一行 也可写成awk 'NR%2==1' 或者 'NR%2'
1   admin     male    21   abc@qq.com     14044534012
3   Root      male    21   hhh@163.com    17048792453
5   Alex      male    13   fff@xyz.com    14646324234
7   echo      female  26   kkksa@126.com  18353673544
9   Steven    female  23   bc@qq.com      13463645644
11  root      male    29   bcbd@139.com   13345423453
awk '!(i=!i)' test.txt #第一次赋值i为空,即布尔假,然后取反后为布尔真,再次取反为布尔假,不打印第一行 也可写成awk 'NR%2==0' 或者 '!(NR%2)'
2   guest     female  24   def@gmail.com  15084545203
4   user      male    21   www@189.com    18234532345
6   baby      female  25   ggg@139.com    18636224672
8   zhangsan  female  22   bax@hanjy.com  14723454343
10  wangwu    male    29   bcbd@139.com   13345423453

小实验,取出文件中第四列的奇数行和偶数行,不要非数字的行

awk '($4+0)%2 && $4~/[0-9]+/' test.txt  #偶数行   等价于'($4+0)%2==0 && $4~/[0-9]+/'  # $4%2==0 为布尔值的判断,真的就输出,假的不输出  注意隐式转换的问题
2   guest     female  24   def@gmail.com  15084545203
7   echo      female  26   kkksa@126.com  18353673544
8   zhangsan  female  22   bax@hanjy.com  14723454343

awk '($4+0)%2 && $4~/[0-9]+/' test.txt    #奇数行 等价于'($4+0)%2==1 && $4~/[0-9]+/'  
1   admin     male    21   abc@qq.com     14044534012
3   Root      male    21   hhh@163.com    17048792453
4   user      male    21   www@189.com    18234532345
5   Alex      male    13   fff@xyz.com    14646324234
6   baby      female  25   ggg@139.com    18636224672
9   Steven    female  23   bc@qq.com      13463645644
10  wangwu    male    29   bcbd@139.com   13345423453
11  root      male    29   bcbd@139.com   13345423453

运算符优先级

优先级从高到低:

()
$     
++  --
^   **
+   -   !   
*   /   %
+   -
space      # 这是字符连接操作 `12 " " 23` `12 " " -23`
|   |&
<   >   <=    >=   !=    ==   
~   !~
in
&&
||
?:
=   +=   -=   *=   /=    %=   ^=

流程控制

注意:awk语句是没有作用域的,全部都是全局变量

if语句

if…else语句也可以多分支例如if…else if…else语句

语法格式:if(){}else{}亦或者if()else if(){}else if(){}else{} 最后的else是可以省略的

if(布尔真){print 布尔真}else

awk 'BEGIN{ a=(11>9); if(a){print "true"}else{print "false"}}'
true

三目运算符

语法: expr1 ? expr2 : expr3

逻辑上与if是一样的 如果expr1 为真 就执行 expr2 如果expr1 为假 就执行expr3

需要注意的是虽然逻辑上是一样的,但是三目运算中只能写表达式,必须要有返回值!!!

例如:

awk 'BEGIN{a=50;b=(a>60) ? "及格" : "不及格";print(b)}' #以为赋值的优先级最低,所以先运行三目运算后,才会赋值给b  等价于b=((a>60) ? "及格" : "不及格")
  不及格
awk 'BEGIN{a=70; a>60 ? b="及格" : b="不及格";print(b)}'
  及格

while和do while循环语句

语法:
while(条件){
   执行代码块
}


do {
   执行代码块
} while(条件)

while先判断条件再决定是否执行代码块,do…while先执行代码块再判断条件决定下次是否再执行代码块

在大多数时候,while和do…while是等价的,但如果第一次条件判断失败,则do…while和while不同。

do while 语句不管条件是否为真,最少会执行一次

do while 用的频率较低

#while 
awk 'BEGIN{i=1;while(i==3){print i;i++}}'
#输出为空因为条件判断失败

#do while
awk 'BEGIN{i=1;do {print i;i++}while(i==3)}'
1  #输出为1 ,因为do while 语句不管条件是否为真会先执行一次代码段

for循环语句

#语法:  C语言写法
for (expr1;expr2;expr3){代码块}
#例如:
awk 'BEGIN{for (i=0;i<3;i++){print i}}'
0
1
2

#死循环
for(;;){print hello}

break和continue

break可退出for、while、do…while、switch语句。

awk 'BEGIN{for(i=1;i<10;i++){if(i==3){break};print i}}'
1
2
#当i等于3时退出循环

continue可让for、while、do…while进入下一轮循环。

[root@sea awk]#: awk 'BEGIN{for(i=1;i<10;i++){if(i==3){continue};print i}}'
1
2
4
5
6
7
8
9
# 当i等于3时会跳过本次循环进入下次循环

next和nextfile

next会在当前语句处立即停止后续操作,并读取下一行,进入循环顶部。

例如,输出除hello外的所有行

echo  -e 'HELLO\nworld\nhello\nWORLD'|awk '/hello/{next}{print}'
HELLO
world
WORLD

nextfile会在当前语句处立即停止后续操作,并直接读取下一个文件,并进入循环顶部。

例如,第一个文件只输出前2行

awk 'NR==3{nextfile}{print}' /etc/passwd  test.txt 
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest     female  24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
4   user      male    21   www@189.com    18234532345
5   Alex      male    13   fff@xyz.com    14646324234
6   baby      female  25   ggg@139.com    18636224672


#由于NR是所有文件的行号计数器,所以在读取第二个文件的时间不会重置NR值
#如果每个文件都只想输出两行的话请用FNR

awk 'FNR==3{nextfile}{print}' /etc/passwd  test.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012

exit

直接退出awk程序,而且还可以给出一个退出的状态码

注意,END语句块也是exit操作的一部分,所以在BEGIN或main段中执行exit操作,也会执行END语句块。

 如果exit在END语句块中执行,则立即退出
echo "hello HELLO world WORLD"|awk '{print $1;exit;print $2}END{print $3 ;exit;print $4}'
hello #$1
world #$3

​ 如果真的想直接退出整个awk,则可以先设置一个变量,然后在END语句块的开头检查这个变量再exit。

awk 'BEGIN{print "begin_code";var=1;exit}END{if(var){exit}else{print "end_code"}}'
begin_code

exit状态码

exit可以指定退出状态码,如果触发了两次exit操作,即BEGIN或main中的exit触发了END中的exit,如果END代码中有exit 状态码则返回END代码中的exit 状态码, 如果END中 的exit没有指定退出状态码时,则采取前一个退出状态码。

awk 'BEGIN{print "begin_code";var=1;exit 3}END{if(var){exit }else{print "end_code"}}'
begin_code
echo $?
3 #状态码为3

awk 'BEGIN{print "begin_code";var=1;exit 3}END{if(var){exit 4}else{print "end_code"}}'
begin_code
echo $?
4  #返回END代码中的exit 状态码,状态码为4 

awk预定义变量(gawk 4.2)


类型 预定义变量 解释 备注
控制awk工作类 RS 记录分隔符(行分隔符) 默认是\n, awk读取输入文件时,每次读取一条记录(record)(默认情况下按行读取,所以此时记录就是行)。每读取一条记录,将其保存到 $0 中,然后执行一次main代码段
携带信息类 NR 所有文件的行号计数器 处理单文件时效果与FNR 一样, 处理多文件时,会累计记录多文件的行号不会重置行号
携带信息类 FNR 为各个文件的行号计数器 处理单文件时效果与NR 一样, 处理多文件时,不会累计记录多文件的行号没读完一个文件后,FNR重置
携带信息类 NF 记录当前字段数量(列数量) awk读取每一条记录之后,会将其赋值给 $0 ,同时还会对这条记录按照预定义变量FS划分字段,将划分好的各个字段分别赋值给 $1 $2 $3 $4...$N ,同时将划分的字段数量赋值给预定义变量NF
控制awk工作类 FS或者-F 输入字段分割符(列分隔符) FS为单个字符时,该字符即为字段分隔符,FS为多个字符时,则采用正则表达式模式作为字段分隔符
控制awk工作类 IGNORECASE 忽略大小写 IGNORECASE在等于非零值时,在所有正则匹配时忽略大小写 (只影响正则,所以FS和RS等为单字时无影响)
控制awk工作类 FIELDWIDTHS 字段分隔符(根据字符宽度分割字段) gawk提供的高级功能。用于处理某字段缺失
控制awk工作类 FPAT 取得匹配字符部分作为字段(正则表达式) gawk提供的高级功能。常用于字段中含有字段分割符的场合,匹配为正则表达式
控制awk工作类 OFS 输出字段分隔符(列分隔符) OFS 默认值为空格,用来连接输出字段
控制awk工作类 OFMT 输出格式(浮点数) 默认为%.6g 表示有效位最多为6为(整数+小数,四舍五入) %.Nf 小数最多为N位 见printf 格式化字符
控制awk工作类 ORS 记录输出分隔符(行分隔符) 默认值是\n
控制awk工作类 CONVFMT 数字转换为字符串之后,输出的格式 默认为%.6g
携带信息类 SUBSEP arr[x,y] 中下标分隔符构建成索引时对应的字符 默认值为 \034 是一个不太可能出现在字符串中的不可打印字符
携带信息类 FILENAME 正在处理的文件 awk当前正在处理的文件(命令行中指定的文件),所以在BEGIN中该变量值为空
携带信息类 ARGC 命令行参数的数量 预定义变量ARGC初始时是ARGV数组的长度,即命令行参数的数量。
携带信息类 ARGV 保存了所有命令行参数 预定义变量ARGV是一个数组,包含了所有的命令行参数。该数组使用从0开始的数值作为索引。
携带信息类 ENVIRON 保存了shell的环境变量数组 例如:awk ‘BEGIN{print ENVIRON[“HOME”];}’ #/root 将打当前用户的家目录
携带信息类 ARGIND 正在处理的文件在ARGV中的索引位置 如果awk正在处理命令行参数中的某文件,则 ARGV[ARGIND] == FILENAME 一定为为真
携带信息类 RLENGTH match()函数正则匹配成功时,所匹配到的字符串长度,如果匹配失败,该变量值为-1
携带信息类 RSTART match()函数匹配成功时,其首字符的索引位置,如果匹配失败,该变量值为0 match()函数正则匹配成功时,match()函数的返回值就是RSTART

RS 预定义变量


  • RS两种可能情况:

    • RS为单个字符:直接使用该字符来分割记录
    • RS为多个字符:将其当做正则表达式,只要匹配正则表达式的符号,都用来分割记录
  • 设置预定义变量IGNORECASE为非零值,正则匹配时表示忽略大小写

  • 特殊的RS值用来解决特殊读取需求:

    • RS="" :按段落读取
    • RS="\0" :一次性读取所有数据,但有些特殊文件中包含了空字符 \0
    • RS="^$" :真正的一次性读取所有数据,因为非空文件不可能匹配成功
    • RS="\n+" :按行读取,但忽略所有空行
    # 按段落读取:RS=''
     awk 'BEGIN{RS=''}{print $0"------"}' test.txt     
    # 一次性读取所有数据:RS='\0' RS="^$"
     awk 'BEGIN{RS='\0'}{print $0"------"}' test.txt     
     awk 'BEGIN{RS='^$'}{print $0"------"}' test.txt  
    # 忽略空行:RS='\n+'
     awk 'BEGIN{RS='\n+'}{print $0"------"}' test.txt 
    # 忽略大小写:预定义变量IGNORECASE设置为非0值
     awk 'BEGIN{IGNORECASE=1}{print $0}' RS='[ab]' test.txt  
    

把RS记录分割符设置成一个正则,匹配一个或多个数字的字符段为记录分割符。RT就是当RS为正则表达式时的匹配到的每个记录的分割符的内容即为RT变量表示

NF预定义变量


awk读取每一条记录之后,会将其赋值给 $0 ,同时还会对这条记录按照预定义变量FS划分字段,将划分好的各个

字段分别赋值给 $1 $2 $3 $4...$N ,同时将划分的字段数量赋值给预定义变量NF


$N 引用字段:

N=0 :即 $0 ,引用记录本身

0<N<=NF :引用对应字段

N>NF :表示引用不存在的字段,返回空字符串

N<0 :报错

可使用变量或计算的方式指定要获取的字段序号。

awk '{n = 3;print $n}' test.txt
awk '{print $(1+4)}' test.txt # 注意小括号,用于改变优先级
awk '{print $(NF-5)}' test.txt

分割字段的方式【FS|FIELDWIDTHS|FPAT】


读取record(记录)之后,将使用预定义变量FS、FIELDWIDTHS或FPAT中的一种来分割字段。分割完成之后,再进入

main代码段(所以,在main中设置FS对本次已经读取的record(记录)是没有影响的,但会影响下次读取)

FS 预定义变量和-F选项


  • FS为单个字符时,该字符即为字段分隔符

  • FS为多个字符时,则采用正则表达式模式作为字段分隔符

    • 例如使用空格和l 作为分割符,
     echo "h ello"|awk 'BEGIN{ FS="[ l]+"}{print $1,"--",$2,"--",$3}' #也可以写成'BEGIN{ FS="( )+|l+"} 小括号内有空格
    #结果如下:
    h -- e -- o
    #做为分隔符的字符串不可见(截取时会丢弃)!!!
    
  • 特殊的,也是FS默认的情况,FS为单个空格时,将以**连续的空白(空格、制表符、换行符)**作为字段分隔符

  • 特殊的,FS为空字符串""时,将对每个字符都进行分隔,即每个字符都作为一个字段**(使用空字符串""时,不能使用-F选项指定会报错,用BEGIN{})**

    echo "hello"|awk 'BEGIN{ FS=""}{print $1;print $2;print $3;print $4;print $5}'
    #结果如下:
    h
    e
    l
    l
    o
    
  • 设置预定义变量IGNORECASE为非零值,正则匹配时表示忽略大小写(只影响正则,所以FS为单字时无影响)

    • echo "h eLlo"|awk 'BEGIN{FS="[ l]+";IGNORECASE = 1}{print $1,"--",$2,"--",$3}'
      #结果如下:
      h -- e -- o
      
  • 如果record(记录)中无法找到FS指定的分隔符(例如将FS设置为"\n"),则整个记录(默认为行)作为一个字段,即 $1$0相等

FIELDWIDTHS预定义变量


指定预定义变量FIELDWIDTHS按字符宽度分割字段,这是gawk提供的高级功能。在处理某字段缺失时非常好用。

用法:

  • FIELDWIDTHS=“2 4 7”

    • 表示第一个字段2字符,第二字段4字符…

    • echo "AAAAAAAAAAAAAAAAAAAAAA"|awk 'BEGIN{FIELDWIDTHS=" 4 5 6"}{print $1;print $2;print $3}'
      #结果如下:
      AAAA
      AAAAA
      AAAAAA
      
      
  • FIELDWIDTHS = “6 2:3 6 4:30” 表示:

    • 第一个字段读6个字符
  • 然后跳过2个字符再读3个字符作为第二个字段

  • 然后读6个字符作为第三个字段

  • 然后跳过4个字符在读30个字符作为第四个字段(如果不足30个字符,则读到结尾)

  • FIELDWIDTHS=“4 6 *” :

    • 第一个字段4个字符
  • 第二个字段6个字符

  • 第三个字段剩余所有字符

  • 星号只能放在最后,且只能单独使用,表示剩余所有

  • 设置该变量后,FS失效, 但之后再设置FS或FPAT,该变量将失效

FIELDWIDTHS处理某些字段缺失的数据。

如果按照常规的FS进行字段分割,则对于缺失字段的行和没有缺失字段的行很难统一处理,但使用FIELDWIDTHS则

非常方便。(缺点是比较笨重,需要自己手动计算每个字段中最长的字段数,和分隔符的长度(例如"空格"))

例如:

test.txt文件内容如下:

ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest             24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
4   user      male    21                  18234532345
5   Alex      male    13   fff@xyz.com    14646324234
6   baby      female  25   ggg@139.com    18636224672
7   echo      female       kkksa@126.com  18353673544
8   zhangsan  female  22   bax@hanjy.com  14723454343
9             female  23   bc@qq.com      13463645644
10  wangwu    male    29   bcbd@139.com   13345423453
11  root      male    29   bcbd@139.com   13345423453
 awk 'BEGIN{FIELDWIDTHS="2 2:8 1:7 2:3 2:13 2:11"}{print $1,"--",$2,"--",$3,"--",$4,"--",$5,"--",$6}' test.txt
 #结果如下:
ID -- name     -- gender  -- age --   mail        --  tel
1  -- admin    --  male   -- 21  -- abc@qq.com    -- 14044534012
2  -- guest    --         -- 24  -- def@gmail.com -- 15084545203
3  -- Root     --  male   -- 21  -- hhh@163.com   -- 17048792453
4  -- user     --  male   -- 21  --               -- 18234532345
5  -- Alex     --  male   -- 13  -- fff@xyz.com   -- 14646324234
6  -- baby     --  female -- 25  -- ggg@139.com   -- 18636224672
7  -- echo     --  female --     -- kkksa@126.com -- 18353673544
8  -- zhangsan --  female -- 22  -- bax@hanjy.com -- 14723454343
9  --          --  female -- 23  -- bc@qq.com     -- 13463645644
10 -- wangwu   --  male   -- 29  -- bcbd@139.com  -- 13345423453
11 -- root     --  male   -- 29  -- bcbd@139.com  -- 13345423453

FPAT预定义变量


FS是指定字段分隔符,来取得除分隔符外的部分作为字段。

FPAT是取得匹配的字符部分作为字段。它是gawk提供的一个高级功能

FPAT根据指定的正则来全局匹配record(记录),然后将所有匹配成功的部分组成 $1、$2… ,但是不会修改 $0,相当于将匹配不成功的作为字段分割符 。

echo "aaa,bb3b,4#43cd,55frf66"|awk 'BEGIN{FPAT= "[0-9]+"}{print $1,"--",$2,"--",$3,"--"$4,"--",$5,"-->",$0}'
#结果如下:
3 -- 4 -- 43 --55 -- 66 --> aaa,bb3b,4#43cd,55frf66

之后再设置FS或FPAT,该变量将失效

FPAT常用于字段中包含了字段分隔符的场景。例如:

echo hello,"ni,hao",11,22-22,world #其中逗号为字段分隔符,但双引号包围的是一个整体字段中含有逗号。
echo 'hello,"ni,hao dfs",11,22-22,world'|awk 'BEGIN{FPAT="[^,]+|(\"[^\"]+\")"}{print $1,"--",$2}'
#结果如下:
hello -- "ni,hao dfs"

检查字段分隔的方式PROCINFO


有FS、FIELDWIDTHS、FPAT三种获取字段的方式,可使用 PROCINFO 数组来确定本次使用何种方式获得字段。PROCINFO是一个数组,记录了awk进程工作时的状态信息。

如果:

  1. PROCINFO[“FS”]==“FS” ,表示使用FS分割获取字段
  2. PROCINFO[“FPAT”]==“FPAT” ,表示使用FPAT匹配获取字段
  3. PROCINFO[“FIELDWIDTHS”]==“FIELDWIDTHS” ,表示使用FIELDWIDTHS分割获取字段
if(PROCINFO["FS"]=="FS"){
   print FS
} else if(PROCINFO["FPAT"]=="FPAT"){
   print FPAT	
} else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
   print FIELDWITHS
}

ARGV和ARGC


预定义变量ARGV是一个数组,包含了所有的命令行参数。该数组使用从0开始的数值作为索引。

预定义变量ARGC初始时是ARGV数组的长度(ARGV数组元素的数量),即命令行参数的数量。

ARGV数组的数量和ARGC的值只有在awk刚开始运行的时候是保证相等的。

例如:

awk -v key=var  -F ":" 'BEGIN{print "ARGV元素数量为:"ARGC;for (i in ARGV){print i"--->"ARGV[i]}}' key1=var1 test.txt

ARGV元素数量为:3  
0--->awk		#ARGV[0]
1--->key1=var1	#ARGV[1]
2--->test.txt	#ARGV[2]

awk读取文件是根据ARGC的值来进行的,有点类似于如下伪代码形式:

while(i=1;i<ARGC;i++){
   read from ARGV[i]
}

默认情况下,awk在读完ARGV中的一个文件时,会自动从它的下一个元素开始读取,直到读完所有文件。(从上面伪代码中可以看出)

直接减小ARGC的值,会导致awk不会读取尾部的一些文件。

例如:

awk 'BEGIN{print ARGC}' test1.txt test.txt
3   #此时ARGC有3个参数 分别是awk  test1.txt test.txt

#当修改ARGC的为2时,那么就会丢弃最后的参数,不会读取test.txt
awk 'BEGIN{ARGC=2}{print}' test1.txt test.txt
a 10
b 11
c 12
d 19
a 15
e 17
f 20
g 14
g 18
f 16
b 13

此外,增减ARGC的值,都不会影响ARGV数组,仅仅只是影响awk读取文件的数量,但是让文件读取完成后,依然会退出。

例如:

[root@vm-nfs-41 awk]# awk 'BEGIN{ARGC=5}{print}' test1.txt test.txt
a 10
b 11
c 12
...
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest     female  24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
...

可以将ARGV中某个元素赋值为空字符串"",awk在选择下一个要读取的文件时,会自动忽略ARGV中的空字符串元

素。例如:当把ARGV[2]=""设置为空字符串时,那么他就不会读取对应的值 test1.txt

awk 'BEGIN{ARGV[2]="";print ARGC;for (i in ARGV){print ARGV[i]}}' test.txt test1.txt
3
awk
test.txt

也可以 delete ARGV[i] 的方式来删除ARGV中的某元素。

用户手动增、删ARGV元素时,不会自动修改ARGC,而awk读取文件时是根据ARGC值来确定的。所以在增加ARGV

元素之后,要手动的去增加ARGC的值。例如:

awk 'BEGIN{ARGV[2]="test1.txt"}{print}' test.txt  #它不会去读取test1.txt文件
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest     female  24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
...


awk 'BEGIN{ARGV[2]="test1.txt";ARGC++}{print}' test.txt  #此时手动添加ARGC的值后会进行读取test1.txt 亦可以使用ARGC=3
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest     female  24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
...
a 10
b 11
...

FILENAME

awk当前正在处理的文件(命令行中指定的文件),所以在BEGIN中该变量值为空

awk 'BEGIN{RS="^$"}{print FILENAME}' test.txt test1.txt 

test.txt
test1.txt

ARGIND

​ 正在处理的文件在ARGV中的索引位置。所以,如果awk正在处理命令行参数中的某文件,则 ARGV[ARGIND] == FILENAME 为真 例如:

awk 'BEGIN{RS="^$"}{print ARGIND,"--->",ARGV[ARGIND],"--->",FILENAME}' test.txt test1.txt 
#ARGIND    ARGV[]        FILENAME
   1 ---> test.txt  ---> test.txt
   2 ---> test1.txt ---> test1.txt

修改字段或NF值的联动效应–$0重建


注意下面的分割和计算两词:分割表示使用FS(field Separator),计算表示使用预定义变量OFS(Output

Field Separator)。

  1. 修改 $0 ,将使用 FS 重新分割字段,所以会影响 $1、$2…

    echo hello world |awk 'BEGIN{$0="HELLO WORLD" ;print $1,$2}'
    #结果如下
    HELLO WORLD
    
  2. 修改 $1、$2 ,将根据 $1 到 $NF 来重新计算 $0(即使用OFS重建$0),即使是 $1 = $1 这样的原值不变的修改,也一样会重新计算 $0

    echo  "hello world" |awk 'BEGIN{OFS="--"}{$1="HELLO";print $0}'
    #结果如下:
    HELLO--world
    
  3. 为不存在的字段赋值,将新增字段并按需使用空字符串填充中间的字段,并使用 OFS 重新计算 $0

    echo  "hello world" |awk 'BEGIN{OFS="--"}{$4="HELLO";print $0}'
    #结果如下:
    hello--world----HELLO
    #因字段中没有$3但我们对$4进行了赋值,因此新增的字段使用空字符串填充中间的字段
    
  4. 增加NF值,将使用空字符串新增字段,并使用 OFS 重新计算 $0

    echo  "hello world HELLO WORLD " |awk 'BEGIN{OFS="--"}{NF=(NF+2);print $0}'
    #结果如下:
    hello--world--HELLO--WORLD----
    #注意尾部出现了四个短横线
    
  5. 减小NF值,将丢弃一定数量的尾部字段,并使用 OFS 重新计算 $0

    echo  "hello world HELLO WORLD " |awk 'BEGIN{OFS="--"}{NF=3;print $0}'
    #结果如下:
    hello--world--HELLO
    

关于$0

  • 当读取一条record记录之后,将原原本本地被保存到 $0 当中。

  • 但是,只要出现了上面所说的任何一种导致 $0 重新计算的操作,都会立即使用OFS去重建 $0 。换句话说,没有导致 $0 重建,$0就一直是原原本本的数据,所以指定OFS也无效。

    echo  "hello world HELLO WORLD " |awk 'BEGIN{OFS="--"}{print $0}'
    #结果入下:
    hello world HELLO WORLD 
    
  • $0 重建后,将自动使用OFS重建,所以即使没有指定OFS,它也会采用默认值(空格)进行重建。

    echo  "hello-world-HELLO-WORLD " |awk 'BEGIN{FS="-"}{$1=$1;print $0}'
    #结果如下:
    hello world HELLO WORLD 
    
  • 如果重建$0之后,再去修改OFS,将对当前行无效,但对之后的行有效。所以如果也要对当前行生效,需要再次重建

    #PS: 使用echo -e \n 对输出内容进行了换行
    
    #例1:重建 $0 之后,再去修改OFS,将对当前行无效
    
    echo -e  "test1 test2 test3 test4 \nhello world HELLO WORLD " |awk '{$1=$1"--"; OFS="++";print $0}'
    #结果如下: 不会对第一行使用OFS重建
    test1-- test2 test3 test4
    hello--++world++HELLO++WORLD
    
    #例2:再次重建后对当前行生效
    echo -e  "test1 test2 test3 test4 \nhello world HELLO WORLD " |awk '{$1=$1"--"; OFS="++";$2=$2;print $0}'
    #结果如下:
    test1--++test2++test3++test4
    hello--++world++HELLO++WORLD
    

关注 $0 重建是一个非常有用的技巧。

​ 例如,下面通过重建 $0 的技巧来实现去除行首行尾空格并压缩中间空格

echo "   a      b     c  d   e  f "|awk '{$1=$1;print}'
a b c d e f

echo "   a      b     c  d   e  f "|awk '{$1=$1;print}' OFS="--"
a--b--c--d--e--f

awk函数


函数 解释 备注
getline 从标准输入或非选项型参数所指定的文件中读取数据,还可以从其它各种渠道获取需要处理的数据 主要是把shell命令的数据传递给awk
system 调用系统命令 system()函数执行一个Shell命令,system()的返回值是命令的退出状态码
print 输出
printf 格式化输出
sprintf() 返回格式化输出(可以变量赋值) a=sprintf(%.2f,5.6345)
typeof() 测试变量的数据类型,包括测试变量是否声明 字符串,数值,正则,数组
strtnum() 数据类型转换(字符转换为数字) 会根据OFMT所设定的输出格式(浮点数),进行截取默认为6位(整数+小数)
toupper() 小写字母转换大写 toupper($0)~/HELLO/
tolower() 大写字母转换小写 tolower($0)~/hello/
length() 统计数组的长度(元素个数) 可以统计字符串的数量 length(“string”)
isarray() 检测是否为数组真返回1 假返回0
substr() 从string中截取子串 格式:substr(string,start[,length]) :start是截取的起始索引位(索引位从1开始而非0),length表示截取的子串长度。如果省略length,则表示从start开始截取剩余所有字符。
index() 从str中搜索substr(子串),返回搜索到的索引位置(索引从1开始)
split() 使用定义的fieldsep字符串分割后保存到数组array中,返回值是array的元素个数 split(string, array [, fieldsep] [, seps ]) :将字符串分割后保存到数组arr中,数组索引从1开始存储。并返回分割得到的元素个数
patsplit() 用正则表达式fieldpat匹配字符串string,将所有匹配成功的部分保存到数组array中,返回值是array的元素个数 patsplit(string, array [, fieldpat [, seps ] ]) :用正则表达式fieldpat匹配字符串string,将所有匹配成功的部分保存到数组array中,数组索引从1开始存储。返回值是array的元素个数.( split和patsplit类似于预定义变量FS 和 FPAT)
match() 使用reg匹配string(只匹配每行第一次成功的字符串),返回匹配成功的索引位(预定义变量RSTART的值) match(string,reg[,arr]) :使用reg匹配string(只匹配每行第一次成功的字符串),返回匹配成功的索引位(从1开始计数),匹配失败则返回0。如果指定了arr参数,则arr[0]保存的是匹配成功的字符串,arr[1]、arr[2]、…保存的是各个分组捕获的内容
sub() 从字符串内进行匹配替换 sub(regexp, replacement [, target]) sub()从字符串target中进行正则匹配,并使用replacement对第一次匹配成功的部分进行替换,替换后保存回target中。返回替换成功的次数,即0或1
gsub() 从字符串内进行匹配替换 同sub()函数功能一样,唯一的区别是sub()只会对第一次匹配成功的进行替换,而gsub()会对全部匹配成功的进行替换,即sub()的全局模式(globel)
gensub() 从字符串内进行匹配替换 gawk支持的gensub(),完全可以取代sub()和gsub()语法格式为:gensub(regexp, replacement, how [, target])
mktime() 构建一个时间,返回这个时间点的时间戳,构建失败则返回-1 格式:mktime(“YYYY MM DD HH mm SS [DST]”) 构建一个时间,返回这个时间点的时间戳,构建失败则返回-1
systime() 返回当前系统时间戳 例如:awk ‘BEGIN{print systime()}’
strftime() 将时间按指定格式转换为字符串,并返回转换后的结果 将指定的时间戳tiemstamp按照给定格式format转换为字符串并返回这个字符串。例如:awk ‘BEGIN{print strftime(“%F %T”,“1655692212”)}’

getline


除了可以从标准输入或非选项型参数所指定的文件中读取数据,还可以使用getline从其它各种渠道获取需要处理

的数据,它的用法有很多种。

getline的返回值:

如果可以读取到数据,返回1

如果遇到了EOF,返回0

如果遇到了错误,返回负数。如-1表示文件无法打开,-2表示IO操作需要重试(retry)。

无参数getline


getline无参数时,表示立即读取下一条记录保存到 $0 中,并进行字段分割,然后继续执行后续代码逻辑

此时的getline会设置NF、RT、NR、FNR、$0$1,$2$N

getline和next

getline:读取下一行之后,继续执行getline后面的代码

echo -e "hello\nworld\nHELLO\nWORLD"| awk '/HELLO/{print $0,"第一次打印";getline;print $0,"第二次打印"}'
#结果如下:
HELLO 第一次打印
WORLD 第二次打印
# 在匹配到/HELLO/的记录后, /HELLO/{print $0}会打印当前的$0,当遇到getline后,会立即将下一条数据保存的$0中,然后执行后续代码print $0,打印重建后的$0->WORLD

next:next会在当前语句处立即停止后续操作,并读取下一行,进入循环顶部(类似于shell中的continue),不会再执行next后面的代码

echo  "HELLO hello world HELLO WORLD" | awk 'BEGIN{RS=" "}/HELLO/{print $0,"第一次打印";next;print $0,"第二次打印"}'
#结果如下:
HELLO 第一次打印
HELLO 第一次打印

getline 括号注意事项

getline < 0 表示的是输入重定向

(getline) < 0 表示数值比较(结合if判断使用)

echo -e "hello\nHELLO\nworld\nHELLO"|awk '/HELLO/{print $0,"这个第一个print";if( (getline)<=0 ){exit};print $0,"这个第二个print"}'
#使用if判断结果如下:
HELLO 这个第一个print
world 这个第二个print
HELLO 这个第一个print
#会判断getline的返回值如果返回值小于等于0 执行退出,否则print $0

echo -e "hello\nHELLO\nworld\nHELLO"|awk '/HELLO/{print $0,"这个第一个print";getline;print $0,"这个第二个print"}'
#未使用判断结果如下
HELLO 这个第一个print
world 这个第二个print
HELLO 这个第一个print
HELLO 这个第二个print
#未使用判断在最后一行输出后,因后面没有数据,getline无法将下一行数据存入$0,导致$0数据无变化,直接二次打印$0

一个参数的getline


没有参数的getline是读取下一条记录之后将记录保存到 $0 中,并对该记录进行字段的分割。

一个参数的getline是将读取的记录保存到指定的变量当中并且不会对其进行分割,此时的getline只会设置RT、NR、FNR变量和指定的变量var。$0$N以及NF保持不变。

getline var

#例如:
#源文件
cat test.txt
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest             24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
4   user      male    21                  18234532345
5   Alex      male    13   fff@xyz.com    14646324234
6   baby      female  25   ggg@139.com    18636224672
7   echo      female       kkksa@126.com  18353673544
8   zhangsan  female  22   bax@hanjy.com  14723454343
9             female  23   bc@qq.com      13463645644
10  wangwu    male    29   bcbd@139.com   13345423453
11  root      male    29   bcbd@139.com   13345423453


awk 'BEGIN{IGNORECASE=1}/^1/{print $0,"第一次打印";if ( (getline var) <=0 ){exit};print $0,"第二次打印","--->",$1,"--->",var}' test.txt

1   admin     male    21   abc@qq.com     14044534012 第一次打印
1   admin     male    21   abc@qq.com     14044534012 第二次打印 ---> 1 ---> 2   guest             24   def@gmail.com  15084545203
10  wangwu    male    29   bcbd@139.com   13345423453 第一次打印
10  wangwu    male    29   bcbd@139.com   13345423453 第二次打印 ---> 10 ---> 11  root      male    29   bcbd@139.com   13345423453

从指定文件中读取数据


getline < filename :#从指定文件filename中读取一条记录并保存到 $0 中(输入重定向)

会进行字段的划分,会设置变量 $0 $1,$2,…$N NF ,但不会设置变量 NR FNR(行号)

getline var < filename # 从指定文件filename中读取一条记录并保存到指定变量var中(输入重定向)

不会划分字段,不会设置变量 NR FNR NF $0 $N

filename需使用 “双引号” 包围表示文件名字符串,否则会当作变量解析

例如:getline < “test.txt” 。

此外,如果路径是使用变量构建的,则应该使用括号包围路径部分。

例如: getline < dir “/” filename 中使用了两个变量构建路径,这会产生歧义,应当写成 getline <(dir “/” filename) 。

注意,每次从filename读取之后都会做好位置偏移标记,下次再从该文件读取时将根据这个位置标记继续向后读取。

例如,test.txt文件每次匹配到/qq.com/时就读取getline.txt文件。

cat getline.txt 

getline1 第一行
getline2 第二行
getline3 第三行

#1.test.txt文件每次匹配到/qq.com/时就读取getline.txt文件(逐行读取)

awk '/qq.com/{print;getline < "getline.txt";print $0}' test.txt #文件名一定用双引号引起来,否则会当成变量解析
 #结果如下:
1   admin     male    21   abc@qq.com     14044534012 #test.txt文件
getline1 第一行  #getline文件
9             female  23   bc@qq.com      13463645644 #test.txt文件
getline2 第二行   #getline文件

#读取到getline.txt文件后,会自动在文件做好偏移量,下次继续从标记位置读取,此时第二个print $0 为getline重新设置的$0


#2.test.txt文件每次匹配到/qq.com/时就会一次性读取getline.txt文件所有行 

awk '/qq.com/{print; while (getline < "getline.txt"){print $0}}' test.txt
 #结果如下:
1   admin     male    21   abc@qq.com     14044534012
getline1 第一行
getline2 第二行
getline3 第三行
9             female  23   bc@qq.com      13463645644
#注意:因读取到getline.txt文件后,会自动在文件做好偏移量,导致在第二次匹配到/qq.com/的时间会从上次次继续从标记位置读取,但此时的标记偏移量已经到的文件的最后,因此没有输出

#如果需要在每一次匹配到/qq.com/是都读取getline.txt的话,需要使用 close("getline.txt") 在 while(getline) 读取完文件之后关掉,以便后面再次读取,如果不关掉,则文件偏移指针将一直在文件结尾处,使得下次读取时直接遇到EOF。

 awk '/qq.com/{print; while (getline < "getline.txt"){print $0};close("getline.txt")}' test.txt
  #结果如下:
1   admin     male    21   abc@qq.com     14044534012
getline1 第一行
getline2 第二行
getline3 第三行
9             female  23   bc@qq.com      13463645644
getline1 第一行
getline2 第二行
getline3 第三行

使用getline var < "getline.txt"方式读取时与getline < "getline.txt"读取方式一致,只要注意 getline var的方式不会划分字段,不会设置变量 NR FNR NF $0 $N,所以 print时要写成print var

从Shell命令输出结果中读取数据


  • command | getline :从Shell命令command的输出结果中读取一条记录保存到 $0

    • 会进行字段划分,设置变量 $0`` NF $N RT ,不会修改变量 NR FNR
  • command| getline var :从Shell命令command的输出结果中读取数据保存到var中

    • 除了var和RT,其它变量都不会设置

      如果要再次执行command并读取其输出数据,则需要close关闭该命令。例如 close(“seq 1 2”)

      close(“command”)需要与command|getline指定的command一模一样,否则无法关闭!!!

参见下面的示例。

例1:

每次匹配到/qq.com/的行都输出seq 1 2产生的结果 。

 awk '/qq.com/{print $0,"第一个print"; while("seq 1 2"|getline){print $0,"第二个print"};close("seq 1 4") }' test.txt 
1   admin     male    21   abc@qq.com     14044534012 第一个print
1 第二个print
2 第二个print
9             female  23   bc@qq.com      13463645644 第一个print
1 第二个print
2 第二个print

#close("command")需要与command|getline指定的command一模一样

例2:

调用Shell的date命令生成时间并sleep 1秒后,保存到awk变量Date中

awk '/qq.com/{print; "date +%T && sleep 1" | getline Date;{print Date};close("date +%T && sleep 1")}' test.txt
#结果入下:
1   admin     male    21   abc@qq.com     14044534012
17:16:06
9             female  23   bc@qq.com      13463645644
17:16:07


#	将command保存成一个字符串变量后续调用方式

 awk 'BEGIN{DATE="date +%T && sleep 1"}/qq.com/{print;DATE|getline Date;{print Date};close(DATE)}' test.txt
 #结果入下:
1   admin     male    21   abc@qq.com     14044534012
17:21:45
9             female  23   bc@qq.com      13463645644
17:21:46
#注意使用变量时不需要加"双引号" 否则会解析成字符串
#command中可以包含Shell的其它特殊字符,例如管道、重定向符号等

close()


close(filename)
close(cmd,[from | to]) # 默认为from,  to 参数只用于coprocess的第一个阶段
  • 如果close()关闭的对象不存在,awk不会报错,仅仅只是让其返回一个负数返回值。
  • close()有两个基本作用:
    • 关闭文件,丢弃已有的文件偏移指针
      • 下次再读取文件,将只能重新打开文件,重新打开文件会从文件的最开头处开始读取
    • 发送EOF标记

​ awk中任何文件都只会在第一次使用时打开,之后都不会再重新打开。只有关闭之后,再使用才会重新打开。

例如一个需求是只要在a.txt中匹配到1开头的行就输出另一个文件x.log的所有内容,那么在第一次输出x.log文

件内容之后,文件偏移指针将在x.log文件的结尾处,如果不关闭该文件,则后续所有读取x.log的文件操作都从结

尾处继续读取,但是显然总是得到EOF异常,所以getline返回值为0,而且也读取不到任何数据。所以,必须关闭

它才能在下次匹配成功时再次从头读取该文件。

awk '/qq.com/{print; while (getline < "getline.txt"){print $0}}' test.txt
 #结果如下:
1   admin     male    21   abc@qq.com     14044534012
getline1 第一行
getline2 第二行
getline3 第三行
9             female  23   bc@qq.com      13463645644

#close关闭后
awk '/qq.com/{print; while (getline < "getline.txt"){print $0};close("getline.txt") }' test.txt

1   admin     male    21   abc@qq.com     14044534012
getline1 第一行
getline2 第二行
getline3 第三行
9   Steven    female  23   bc@qq.com      13463645644
getline1 第一行
getline2 第二行
getline3 第三行

在处理Coprocess的时候,close()可以指定第二个参数"from"或"to",它们都针对于coprocess而言,from时表

示关闭 coprocess |& getline 的管道,使用to时,表示关闭 awk print |& coprocess的管道。

getline 的协同进程(coporcess)用法


awk虽然强大,但是有些数据仍然不方便处理,这时可将数据交给Shell命令去帮助处理,然后再从Shell命令的执

行结果中取回处理后的数据继续awk处理。

awk通过 |& 符号来支持coprocess

awk {print} |& Shell_Cmd #向协程发送数据
Shell_Cmd |& getline [var] #awk从协程取回数据

这表示awk通过print输出的数据将传递给Shell的命令Shell_command去执行,然后awk再从Shell_command的执行结果

中取回Shell_command产生的数据。

例如,取出test.txt文件中邮箱的后缀,不想使用awk的substr()来取子串,而是使用sed命令来替换

 cat test.txt
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.com     14044534012
2   guest     female  24   def@gmail.com  15084545203
3   Root      male    21   hhh@163.com    17048792453
4   user      male    21   www@189.com    18234532345
5   Alex      male    13   fff@xyz.com    14646324234
6   baby      female  25   ggg@139.com    18636224672
7   echo      female  26   kkksa@126.com  18353673544
8   zhangsan  female  22   bax@hanjy.com  14723454343
9   Steven    female  23   bc@qq.com      13463645644
10  wangwu    male    29   bcbd@139.com   13345423453
11  root      male    29   bcbd@139.com   13345423453


#vim test.awk

#将shell命令写成一个变量
BEGIN{
  SED="sed -nr \"s#^.*@(.*) .*$#\\1#gp\""
}

NR>1{  #首先排除第一行
  print $0 |& SED  					#让awk_print的结果交个shell命令执行
  close(SED,"to")   				#to(向上一个协程)中close(发送一个EOF标记),表示文件已经读取完成
  if ((SED |& getline var)>0){  	#通过getline 将shell的命令取回到awk中
    print var
}
    close(SED)
}


awk -f test.awk test.txt
#结果如下:
qq.com    
gmail.com 
163.com   
189.com   
xyz.com   
139.com   
126.com 
hanjy.com 
qq.com     
139.com  
139.com  

#或者写成
awk 'BEGIN{SED="sed -rn \"s#.*@(.*) .*#\\1#gp\""}NR>1{print $0 |& SED;close(SED,"to");if((SED |& getline var)>0 ){print var};close(SED)}' test.txt

对于 awk{print} |& command; command |& getline 的使用,须注意的是:

  • awk_print |& command 会直接将数据写进管道,command可以从中获取数据

  • 强烈建议在awk {print}写完数据之后加上 close(cmd,“to”) ,这样表示向管道中写入一个EOF标记,避免某些要求读完所有数据再执行的command命令被永久阻塞

  • 对于那些要求读完所有数据再执行的命令,例如sort命令,它们有可能需要等待数据已经完成后(遇到EOF标记才开始执行任务,对于这些命令,可以多次向coprocess协程中写入数据,最后 close(CMD,“to”) 让coprocess运行起来

  • 上面的第一个close是必须的,否则sed会一直阻塞。因为sed一直认为还有数据可读,只有关闭管道发送一个

    EOF,sed才会开始处理。

例如,对age字段(即 $4 )使用sort命令按数值大小进行排序:

首先将命令设置为一个变量(BEGIN代码段),然后直接将数据写进管道,COMMAND从中获取数据(main代码段), 最后在END代码块中用close(COMMAND.“to”)向上一个协程发送EOF标记,表示文件全部读取完毕的标记,取回数据

awk 'BEGIN{COMMAND="sort -rnk4"}NR>1{print $0 |& COMMAND}END{close(COMMAND,"to"); while(COMMAND |& getline){print $0};close(COMMAND)}' test.txt
#结果如下
11  root      male    29   bcbd@139.com   13345423453
10  wangwu    male    29   bcbd@139.com   13345423453
7   echo      female  26   kkksa@126.com  18353673544
6   baby      female  25   ggg@139.com    18636224672
2   guest     female  24   def@gmail.com  15084545203
9   Steven    female  23   bc@qq.com      13463645644
8   zhangsan  female  22   bax@hanjy.com  14723454343
4   user      male    21   www@189.com    18234532345
3   Root      male    21   hhh@163.com    17048792453
1   admin     male    21   abc@qq.com     14044534012
5   Alex      male    13   fff@xyz.com    14646324234


system()


多数时候,使用awk的 print cmd | “sh” 即可实现调用shell命令的功能。

但也可以使用system()函数来直接执行一个Shell命令,system()的返回值是命令的退出状态码。

system()在开始运行之前会flush gawk的缓冲。特别的,空字符串参数的 system(“”) ,它会被gawk特殊对

待,它不会去启动一个shell来执行空命令,而是仅执行flush操作。

awk 'BEGIN{system("echo hello" )}'
hello #echo hello 就会做为“命令行”,由shell来执行

awk 'BEGIN{print( "echo hello")|"bash"}'
hello

print


逗号分隔要打印的字段列表,各字段都会自动转换成字符串格式,然后通过预定义变量OFS(output field separator)的值(其默认值为空格)连接各字段进行输出。

 awk 'BEGIN{OFS="-";print "hello","world"}'
 
hello-world

print要输出的数据称为输出记录,在print输出时会自动在尾部加上输出记录分隔符,输出记录分隔符的预定义变

量为ORS,其默认值为 \n 。

awk 'BEGIN{OFS="-";ORS="++\n";print "hello","world"}'

hello-world++

括号可省略,但如果要打印的元素中包含了特殊符号 > ,则必须使用括号包围(如 print(“a” > “A”) ),因为它

是输出重定向符号。

awk 'BEGIN{OFS="-";ORS="++\n";print "hello" > "world"}' #代表重定向,会在当前目录下生成world文件内容为hello

awk 'BEGIN{OFS="-";print ("hello > world")}' #括号包围表示打印字符串
hello > world

awk 'BEGIN{OFS="-";print ("hello" > "world")}' #数字比较,字符串隐式转换

0

如果省略参数,即 print; 等价于 print $0; 。

print输出数值

对于数值而言,可以自定义转换成字符串的格式,例如使用sprintf()进行格式化。

print在自动转换数值(专指小数)为字符串的时候,采用预定义变量OFMT(Output format)定义的格式按照

sprintf()相同的方式进行格式化。OFMT默认值为 %.6g ,表示有效位(整数部分加小数部分)最多为6。

awk 'BEGIN{print 1.23456789}'

1.23457

#可以修改OFMT,来自定义数值转换为字符串时的格式:
awk 'BEGIN{OFMT="%.2f";print 1.2345678}'

1.23

awk 'BEGIN{OFMT="%d";print 3.99}' #不会进行四舍五入
3

printf


#格式 
printf "format", item1, item2, ...
命令    格式化    参数1   参数2

#格式化的字符数量要与参数一致
awk 'BEGIN{printf "%d %d\n", 3.23, 4.43}'
3 4

格式化字符

%c 将ASCII码转换为字符

%d, %i 转换为整数,直接截断而不会四舍五入

%e, %E 科学计数法方式输出数值

%f, %F 浮点数方式输出,会四舍五入

%g, %G 输出为浮点数或科学计数法格式

%o 将数字识别为8进制,然后转换为10进制,再转换为字符串输出

%s 输出字符串

%x, %X 将数字识别为16进制,然后转换为10进制,再转换为字符串输出

%% 输出百分号%

修饰符:均放在格式化字符的前面

  • N$ N是正整数。默认情况下,printf的字段列表顺序和格式化字符串中的%号顺序是一一对应的,使用N$可以自行指定参数顺序。

    • ​ printf “%2ss %1s”,“world”,"hello"输出hello world
    • ​ N可以重复指定,例如"可以重复指定,例如"%1s %1$s"将取两次第一个字段
  • 宽度 指定该字段占用的字符数量,不足宽度默认使用空格填充,超出宽度将无视。

    • ​ printf “%5s”,“hjy"输出”__hjy",下划线表示空格
  • - 表示左对齐。默认是右对齐的。

    • ​ printf “%5s”,“hjy"输出”__hjy"
    • ​ printf “%-5s”,“hjy"输出"hjy__”
  • 空格 针对于数值。对于正数,在其前添加一个空格,对于负数,无视

    • ​ printf “% d,% d”,3,-2输出"_3,-2",下划线表示空格
  • + 针对于数值。对于正数,在其前添加一个+号,对于负数,无视

    • ​ printf “%+d,%+d”,3,-2输出"+3,-2",下划线表示空格
  • # 可变的数值前缀。对于%o,将添加前缀0,对于%x或%X,将添加前缀0x或0X

  • 0 只对数值有效。使用0而非默认的空格填充在左边,对于左对齐的数值无效

    • ​ printf “%05d”,"3"输出00003
    • ​ printf “%-05d”,"3"输出3
    • ​ printf “%05s”,3输出____3
  • ’ 单引号,表示对数值加上千分位逗号,只对支持千分位表示的locale有效

    • ​ $ awk “BEGIN{printf “%'d\n”,123457890}”
    • ​ 123,457,890
    • ​ $ LC_ALL=C awk “BEGIN{printf “%'d\n”,123457890}”
    • ​ 123457890

重定向输出


print[f] something >“filename”

print[f] something >>“filename”

print[f] something | “Shell_Cmd”

print[f] something |& “Shell_Cmd_Coprocess”

>filename 时,如果文件不存在,则创建,如果文件存在则首先截断。之后再输出到该文件时将不再截断。

awk中只要不close(),任何文件都只会在第一次使用时打开,之后都不会再重新打开。

例如:

 awk '{print $2 >"name.txt";print $4 >"name.txt"}' /tmp/awk/test.txt
cat name.txt

[root@sea ~]#: cat name.txt 
name  	$2
age		$4
admin 	$2
21		$4
guest  	$2
24		$4
Root 	$2
21		$4
user	$2
21		$4
#仅会在第一次打开的时间清空文件,之后都是追加

>>filename 时,将追加数据,文件不存在时则创建。

print[f] something | Shell_Cmd 时,awk将创建一个管道,然后启动Shell命令,print[f]产生的数据放

入管道,而命令将从管道中读取数据。

echo -e "hello 1\nHELLO 2\nworld 4\nWORLD 3\nROOT  6"|awk 'BEGIN{CMD="sort -nrk2 >sort.txt"}{print $2 > "unsort.txt";print $2 | CMD }END{close(CMD)}'

cat unsort.txt
1
2
4
3
6

cat sort.txt
6
4
3
2
1



stdin、stdout、stderr


awk重定向时可以直接使用 /dev/stdin(标准输入) 、 /dev/stdout(标准输出) 和 /dev/stderr(标准错误) 。还可以直接使用某个已打开的文件描述符 /dev/fd/N

awk 'BEGIN{print "hello world" > "/dev/stdout"}'
hello world  #将内容写入到标准输出,等价于awk 'BEGIN{print "hello world" }''


[root@sea awk]#: awk 'BEGIN{print "hello world"  >"/dev/stderr"}'
hello world #定向标准错误,默认显示到屏幕中


awk 'BEGIN{print "hello world" | "cat >&2"}'
hello world #将所有输出到标准错误(默认标准输出,和标准错误定向到屏幕)


awk 'BEGIN{getline < "/dev/stdin";print $0}'
>df  手动输入
 df print $0打印 
#getline 方式实现 将标准写入传入到getline 然后getline将值传入到$0,打印$0


exec 6<> /tmp/awk/test.txt #将test.txt 定义为文件描述符
awk 'BEGIN{while((getline < "/dev/fd/6")>0){print $0}}' #通过文件描述符传入test.txt文件内容.并将其打印

字符串类内置函数

注意,awk中涉及到字符索引的函数,索引位都是从1开始计算,和其它语言从0开始不一样。

sprintf


sprintf()采用和printf相同的方式格式化字符串,但是它不会输出格式化后的字符串,而是返回格式化后的字符

。所以,可以将格式化后的字符串赋值给某个变量。

awk 'BEGIN{var=sprintf("%.3f", 5.22314); print var}'
5.223

#打印需要加print
[root@sea ~]#: awk 'BEGIN{print sprintf("%.3f", 5.22314)}'
5.223

length()

length() :返回字符串字符数量、数组元素数量、或数值转换为字符串后的字符数量

注意:在计算小数时 会根据CONVFMT的值来转换字符串

length() :返回字符串字符数量、数组元素数量、或数值转换为字符串后的字符数量
awk 'BEGIN{print length(1.234567)}'  
7  #CONVFMT的默认值为%.6g 加上点 共计7位

strtonum()

  • 将字符串转换成10进制数值,会根据OFMT所设定的输出格式(浮点数),进行截取默认为6位(整数+小数)
    • 如果strtonum(str)以0开头,则将其视为8进制
    • 如果strtonum(str)以0x 或者0X开头,视为16进制
awk 'BEGIN{OFS="-->";print strtonum("10abc"),strtonum("A123"),strtonum("    1233hello")}'
10-->0-->1233

awk 'BEGIN{OFS="-->";print strtonum("0123abd"),strtonum("0x123")}'
83-->291

toupper()

字符串转换大写

awk 'BEGIN{OFS="-->";print toupper("abcd")}'
ABCD

tolower()

字符串转换小写

awk 'BEGIN{OFS="-->";print tolower("ABCD")}'
abcd

substr()

substr(string,start [,length]) :从string中截取子串

start是截取的起始索引位(索引位从1开始而非0),

length表示截取的子串长度。如果省略length,则表示从start开始截取剩余所有字符。

wk 'BEGIN{var="helloworld";print substr(var,2,5)}'
ellow  #从第二个开始截取5个长度的字符串

awk 'BEGIN{var="helloworld";print substr(var,6)}'
world #从第6个开始截取到最后

注意:

如果start值小于1,则将其看作为1对待,如果start大于字符串的长度,则返回空字符串

如果length小于或等于0,则返回空字符串

index()

从str中搜索substr(子串),返回搜索到第一个的索引位置(索引从1开始),搜索不到则返回0 (可用于判断)

awk 'BEGIN{print index("hello world" , "o")}'
5

awk 'BEGIN{print index("hello world" , "ww")}'
0

split()

split(string, array [, fieldsep] [, seps ]) :将字符串分割后保存到数组array中,数组索引从1开始存储。并返回分割得到的元素个数

其中fieldsep指定分隔符,可以是正则表达式方式的。如果不指定该参数,则默认使用FS作为分隔符,而FS的默认值又是空格。

seps是一个数组,保存了每次分割时的分隔符 相当于seps[]=filedsep

awk 'BEGIN{var="hello-world-HELLO-WORLD-root";print split(var, arr,"-",arr1)}'
5  #返回arr数组元素个数同时得到arr和arr1数组的值 下面例子说明

awk 'BEGIN{var="hello-world-HELLO-WORLD";split(var, arr,"-",arr1);for(i in arr){print i,arr[i],arr1[i]}}'
1 hello -
2 world -
3 HELLO -
4 WORLD 

split在开始工作时,会先清空数组,将split的string参数设置为空,可以用于清空数组。功能等价于:delete arr

awk 'BEGIN{arr[1]=hello;arr[2]=world ;print split("", arr,"-");for(i in arr){print arr[i]}}'
0

如果分隔符无法匹配字符串,则整个字符串当作一个数组元素保存到数组arr中 。

awk 'BEGIN{var="hello-world-HELLO-WORLD";split(var, arr,":",arr1);for(i in arr){print i,arr[i],arr1[i]}}'
1 hello-world-HELLO-WORLD  #arr为1个数组元素,并且arr1的数组元素为空

patsplit()

patsplit(string, array [, fieldpat [, seps ] ]) :用正则表达式fieldpat匹配字符串string,将所有匹配成功的部分保存到数组array中,数组索引从1开始存储。返回值是array的元素个数,即匹配成功了多少次

如果省略fieldpat,则默认采用预定义变量FPAT的值。

例如,取出var=“10a12b13c” 内连续数字的元素个数并打印赋值后的arr元素和值

awk 'BEGIN{var="10a12b13c";print patsplit(var, arr,"[0-9]+");for(i in arr){print i,arr[i]}}'
3     #匹配成功的返回值 攻击匹配成功3个 array数组元素=3
1 10
2 12
3 13

match()

match(string,reg[,arr]) :使用reg匹配string,返回匹配成功的索引位(就是RSTART的值)(从1开始计数),匹配失败则返回0。如果指定了arr参数,则arr[0]保存的是匹配成功的字符串,arr[1]、arr[2]、…保存的是各个分组捕获的内容

  • match匹配时,同时会设置两个预定义变量:RSTART和RLENGTH
    • 匹配成功时:
      • RSTART赋值为匹配成功的索引位,从1开始计数
      • RLENGTH赋值为匹配成功的字符长度
    • 匹配失败时:
      • RSTART赋值为0
      • RLENGTH赋值为-1

例如:

awk 'BEGIN{var="hello2world3HELLO3WORLD";print match(var,"[0-9]",arr);for (i in arr){print arr[i]}}'
6 #返回值为6 即从第六个字符串开始匹配成功

#匹配world644
aawk 'BEGIN{var="hello2world644HELLO03WORLD"; print match(var,"(w.*d)([0-9]+)",arr);print RLENGTH; for (i=0;i<=2;i++){print i,"----", arr[i]}}' # 其中i<=2 与 match("(w.*d)([0-9]+)")分组捕获数量相同

7 #返回值为7 即从第7个字符串开始匹配成功 即RSTART预定义变量的值
8 #RLENGTH的值 即匹配成功的字符串长度
0 ---- world644  #arr[0] 匹配成功的字符串
1 ---- world     #arr[1] 第1个正则分组捕获的内容,即(w.*d)表达式所匹配的内容
2 ---- 644      #arr[2] 第2个正则分组捕获的内容,即([0-9])+表达式所匹配的内容

sub() 和gsub()

语法:sub(regexp, replacement [, target])

  gsub(regexp, replacement [, target]) :sub()的全局模式

sub()从字符串target中进行正则匹配,并使用replacement对第一次匹配成功的部分进行替换,替换后保存回target中。返回替换成功的次数,即0或1(对于gsub则返回实际匹配成功的次数)。

target必须是一个可以赋值的变量名、$N或数组元素名,以便用它来保存替换成功后的结果。不能是字符串字面量,因为它无法保存据。

#sub()

awk  'BEGIN{var="hello world hello world";sub_num=sub(/hello/,"HELLO",var);print "匹配次数:"sub_num;print var }'
匹配次数:1
HELLO world hello world


#gsub()
awk  'BEGIN{var="hello world hello world";sub_num=gsub(/hello/,"HELLO",var);print "匹配次数:"sub_num;print var}'
匹配次数:2
HELLO world HELLO world

如果省略target,则默认使用 $0

#例如:
awk  '{gsub(/com/,"HELLO");print}' /tmp/awk/test.txt
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.HELLO     14044534012
2   guest     female  24   def@gmail.HELLO  15084545203
3   Root      male    21   hhh@163.HELLO    17048792453
2   guest     female  24   def@gmail.HELLO  15084545203
4   user      male    21   www@189.HELLO    18234532345
...

需要注意的是,如果省略target,或者target是$1,$2...$N,那么替换成功后将会使用OFS重新计算 $0

#例如
awk  '{gsub(/com/,"HELLO",$5);print $0}' test.txt

ID  name     gender   age    mail          tel
1 admin male 21 abc@qq.HELLO 14044534012
2 guest female 24 def@gmail.HELLO 15084545203
3 Root male 21 hhh@163.HELLO 17048792453
2 guest female 24 def@gmail.HELLO 15084545203
4 user male 21 www@189.HELLO 18234532345
...

在replacement参数中,可以使用一个特殊的符号 & 来引用匹配成功的部分。注意sub()和gsub()不能在replacement中使用反向引用 \N即正则的分组捕获 。

#例如:
awk  'BEGIN{var="hello world hello world";sub(/hello/,"匹配成功前面 & 匹配成功后面",var);print var }'
#结果如下 
匹配成功前面 hello 匹配成功后面 world hello world

如果想要在replacement中使用 & 纯字符,则转义即可。

两根反斜线:

因为awk在正则开始工作时,首先会扫描所有awk代码然后编译成awk的内部格式,扫描期间会解析反斜线转

义,使得 \ 变成一根反斜线。当真正开始运行后,sub()又要解析,这时 & 才表示的是对&做转义。 扫描

代码阶段称为词法解析阶段,运行解析阶段称为运行时解析阶段。

gensub()

gawk支持的gensub(),完全可以取代sub()和gsub()。语法格式为:

gensub(regexp, replacement, how [, target]) 

从字符串target中进行正则匹配,并使用replacement对匹配成功的部分进行替换,成功会返回给gensub()函数,如果失败则返回target

how指定替换第几个匹配,例如指定为1表示只替换第1个匹配。还可以指定为 g 或 G 开头的字符串,表示全局替换。

这和sub()、gsub()不一样,sub()、gsub()返回的是替换成功的次数。

而gensub()返回替换后得到的结果,而target不变,如果匹配失败,则返回target。

例如:

#匹配成功
awk 'BEGIN{var="hello world hello world";print gensub(/hello/,"HELLO","g",var)}'
HELLO world HELLO world  #gensub()返回替换后得到的结果

#匹配失败
awk 'BEGIN{var="hello world hello world";print gensub(/hhello/,"HELLO","g",var)}'
hello world hello world #此时返回的是var的值

#将文件内的com替换为COM
awk '{print gensub(/com/,"COM","g")}' /tmp/awk/test.txt
ID  name     gender   age    mail          tel
1   admin     male    21   abc@qq.COM     14044534012
2   guest     female  24   def@gmail.COM  15084545203
3   Root      male    21   hhh@163.COM    17048792453
2   guest     female  24   def@gmail.COM  15084545203
4   user      male    21   www@189.COM    18234532345
...

gensub()的replacement部分可以使用 \N 来引用正则分组匹配的结果,而sub()、gsub()不允许使用反向引用。而且,gensub()在replacement部分也还可以使用 & 或 \0 来表示匹配的整个结果。

例如:

awk 'BEGIN{var="hello world";print gensub(/(.*) (.*)/,"\\2 \\1","g",var)}'
world hello # 将hello world 替换 world hello

数值类内置函数

int(expr)     截断(不会进行四舍五入)取整数:int(123.45)和int("123hello")都返回123,int("hello123")返回0
sqrt(expr)    返回平方根
rand()        返回[0,1]之间的随机数,默认使用srand(1)作为种子值 包含0但不包含1
srand([expr]) 设置rand()种子值,省略参数时将取当前时间的epoch时间戳值(精确到秒的epoch)作为种子值
# int()取整

awk 'BEGIN{print int("123hello");print int("hello123");print int(234.67768)}'
#结果如下
123
0
234
# sqrt() 返回平方根

awk 'BEGIN{print sqrt(9);print sqrt(100)}'
3
10
#rand()和srand() 
awk 'BEGIN{print rand()}' 等价于 awk 'BEGIN{srand(1);print rand()}' 
0.924046  #数值为固定的

awk 'BEGIN{srand();print rand()}'
0.429693
awk 'BEGIN{srand();print rand()}'  # 以时间戳作为rand()的种子值,那rand()将每一秒更换一次
0.367464

#取10-20的随机数.包括10和20
awk 'BEGIN{srand();print 10 + int(11*rand())}'
20
awk 'BEGIN{srand();print 10 + int(11*rand())}'
6




时间类内置函数

awk常用于处理日志,它支持简单的时间类操作。有下面3个内置的时间函数:

mktime("YYYY MM DD HH mm SS [DST]") :#构建一个时间,返回这个时间点的时间戳,构建失败则返回-1
systime() :#返回当前系统时间戳
strftime([format [, timestamp [, utc-flag] ] ]) :#将时间按指定格式转换为字符串并返回转的结果字符串

注意,awk构建时间时都是返回秒级的epoch值,即时间戳

systime()

systime() :#返回当前系统时间戳
awk 'BEGIN{print systime()}'
1655692760

mktime()

mktime(“YYYY MM DD HH mm SS [DST]”) :#构建一个时间,返回这个时间点的时间戳,构建失败则返回-1

mktime("YYYY MM DD HH mm SS [DST]") :#构建一个时间,返回这个时间点的时间戳,构建失败则返回-1
awk 'BEGIN{print mktime("2022 6 20 10 30 12")}'
1655692212 #返回构建时间的时间戳

awk 'BEGIN{print mktime("2022 6 20 10 30 ")}'
-1  #如果构建失败返回-1

​ mktime在构建时间时,如果传递的DD给定的值超出了月份MM允许的天数,则自动延申到下个月。例如,指定"2022 5 32 1 10 19"中5月为31天,所以构建出来的时间是2022-06-1 1:10:19 。

awk 'BEGIN{print mktime("2022 05 32 1 10 19") | "xargs -i date -d@{} +\"%F %T\""}'
2022-06-01 01:10:19

此外,其它部分也不限定必须在范围内。例如,2022 5 32 1 10 69 的秒超出了19,那么多出来的秒数将进位到分钟

awk 'BEGIN{print mktime("2022 05 32 1 10 69") | "xargs -i date -d@{} +\"%F %T\""}'
2022-06-01 01:11:09

如果某部位的数值为负数,则表示在此时间点基础上减几。例如:

awk 'BEGIN{print mktime("2022 05 31 -1 10 6") | "xargs -i date -d@{} +\"%F %T\""}'
2022-05-30 23:10:06

strftime()

将指定的时间戳tiemstamp按照给定格式format转换为字符串并返回这个字符串。

如果省略timestamp,则对当前系统时间进行格式化。

如果省略format,则采用PROCINFO[“strftime”]的格式,其默认格式为%a %b %e %H:%M%:S %Z %Y。该格式对应于Shell命令 date 的默认输出结果

awk 'BEGIN{print strftime("%F %T","1655692212")}'
2022-06-20 10:30:12

#也可以让结果交个shell处理
#方式一 使用|管道符
awk 'BEGIN{print mktime("2022 06 20 10 30 12")|"xargs -i date -d@{} +\"%F %T\""}'
2022-06-20 10:30:12
#方式二 使用system()函数
awk 'BEGIN{Date=mktime("2022 06 20 10 30 12");system("date \"+%F %T\" -d@"Date)}'
2022-06-20 10:30:12
时间格式

支持的格式包括:

%a 星期几的缩写,如Mon、Sun Wed Fri

%A 星期几的英文全名,如Monday

%b 月份的英文缩写,如Oct、Sep

%B 月份的英文全名,如February、October

%C 2位数的世纪,例如1970对应的世纪是19

%y 2位数的年份(00–99),通过年份模以100取得,例如2019/100的余数位19

%Y 四位数年份(如2015)

%m 月份(01–12)

%j 年中天(001–366)

%d 月中天(01–31)

%e 空格填充的月中天

%H 24小时制的小时(00–23)

%I 12小时制的小时(01–12)

%p 12小时制时的AM/PM

%M 分钟数(00–59)

%S 秒数(00–60)

%u 数值的星期几(1–7),1表示星期一

%w 数值的星期几(0–6),0表示星期日

%W 年中第几周(00–53)

%z 时区偏移,格式为"+HHMM",如"+0800"

%Z 时区偏移的英文缩写,如CST

%k 24小时制的时间(0-23),1位数的小时使用空格填充

%l 12小时制的时间(1-12),1位数的小时使用空格填充

%s 秒级epoch

##### 特殊符号

%n 换行符

%t 制表符

%% 百分号%

##### 等价写法:

%x 等价于"%A %B %d %Y"

%F 等价于"%Y-%m-%d",用于表示ISO 8601日期格式

%T 等价于"%H:%M:%S"

%X 等价于"%T"

%r 12小时制的时间部分格式,等价于"%I:%M:%S %p"

%R 等价于"%H:%M"

%c 等价于"%A %B %d %T %Y",如Wed 30 Oct 2015 12:34:48 AM CST

%D 等价于"%m/%d/%y"

%h 等价于"%b"

日期时间扩展(GAWK)

使用扩展的方式:

awk -l ext_name 'BEGIN{}{}END{}'
awk '@load "ext_name";BEGIN{}{}END{}'

"time"扩展提供了两个函数。

  • gettimeofday() 获取当前系统时间,以浮点数方式返回,精确的浮点小数位由操作系统决定

  • awk '@load "time";BEGIN{printf "%.3f\n",gettimeofday()}'
    1655706533.659
    
  • sleep(sec) 睡眠指定时间,可以是小数秒

awk '@load "time";BEGIN{sleep(1.2);print "hello world"}'

system(sleep 2)和扩展内的sleep区别:

system(sleep 2):awk会开启子进程的方式来运行

扩展内的sleep: 会在awk内直接运行,不会产生子进程,当使用ctrl+c结束时,会直接结束awk进程

自定义函数

可以定义一个函数将多个操作整合在一起。函数定义之后,可以到处多次调用,从而方便复用。

使用function关键字来定义函数:

function func_name([参数]){
   function_body
}

对于gawk来说,也支持func关键字来定义函数。

func func_name(){} 

注意:无论函数定义在哪里,都能在任何地方调用,因为awk在BEGIN之前,会先编译awk代码为内部格式,在这个阶段会将所有函数都预定义好。

awk 'func  BEGIN{}func  MAIN{}func  END{}func'  #函数可以定义在以下有func的地方
#例如:
awk 'BEGIN{func1()};function func1(){ print "hello";print "world"}'
hello
world

函数的return语句

如果想要让函数有返回值,那么需要在函数中使用return语句。return语句也可以用来立即结束函数的执行。例如:

awk 'BEGIN{print fun()} function fun(){print "hello";return 2; print "world"}'
hello  
2

如果不使用return或return没有参数,则返回值为空,即空字符串。

awk 'BEGIN{print fun()} function fun(){print "hello";return; print "world"}'
hello
    #此处返回空字符串

函数参数

为了让函数和调用者能够进行数据的交互,可以使用参数。

awk 'BEGIN{print fun(10,30)} function fun(a,b){print a; print b;return a+b}'
10
30
40

例如:实现一个重复某字符串指定次数的函数:

awk '
	function fun(str,num){
		for(i=1;i<=num;i++){
			str1=str1""str
		}
		return str1
	}
	BEGIN{
		print fun("~",10)
	}
'

~~~~~~~~~~

参数数据类型冲突问题

如果函数内部使用参数的类型和函数外部变量的类型不一致,会出现数据类型不同而导致报错

例如:错误案例函数内部参数对应的是数组,那么外面对应的也必须是数组类型

awk '
   function fun(a){
       a[1]="hello"
   }
   BEGIN{
       a="hello world"
       fun(a) # 报错
       fun(x)
       x=10   # 报错
   }
'
。

关于参数的按值传递和引用传递

  • 在调用函数时,将数据作为函数参数传递给函数时,有两种传递方式:

    • 传递普通变量时,是按值拷贝传递

      • 直接拷贝普通变量的到函数中

      • 函数内部数据的修改不会影响到外部

      • #例如:
        awk 'function fun(i){i="hello";print i}BEGIN{i="world"; fun(i);print i} '
        hello  #fun函数内部(i)的值
        world  #BEGIN代码块中fun(i)的值
        
    • 传递数组时,是按引用传递

      • 函数内部数据的修改会影响到外部

      • awk 'function fun(arr){arr[i]="hello";print arr[i]} BEGIN{arr[i]="world";fun(arr);print arr[i]}'
        hello  #函数内print arr[i]
        hello  #BEGIN代码块内print arr[i]
        

awk作用域问题

awk只有在函数参数中才是局部变量,其它地方定义的变量均为全局变量。

​ 函数参数会遮盖全局同名变量,所以在函数执行时,无法访问到或操作与参数同名的全局变量,函数退出时会自动撤掉遮盖,这时才能访问全局变量。所以函数的参数具有局部效果

函数内部新增的变量是全局变量,会影响到全局,所以在函数退出后仍然能访问。例如。

awk '
	function fun(str,num){
		for(i=1;i<=num;i++){
			str1=str1""str
		}
		return str1
	}
	BEGIN{
		print fun("~",10)
		print fun("_",10)
	}
'
#结果如下:
~~~~~~~~~~
~~~~~~~~~~__________

​ 可以看到在第二次输出是还是会输出“~” 因为str1变量为函数体内新增变量,属于全局变量,当函数退出后,变量str1依然生效,如果想要str1变量在函数退出后销毁,可以在fun(str,num)参数处传入,因为awk只有在函数参数中才是局部变量。

例如:

awk '
	function fun(str,num         ,str1){
		for(i=1;i<=num;i++){
			str1=str1""str
		}
		return str1
	}
	BEGIN{
		print fun("~",10)
		print fun("_",10)
	}
'

#结果如下
~~~~~~~~~~
__________

#此时在第一次函数结束后,str1参数会立即销毁

awk对函数参数列表做了两类区分:

  • arguments:调用函数时传递的参数
  • local variables:调用函数时省略的参数

为了区分arguments和local variables,约定俗成的,将local variables放在一大堆空格后面来提示用户。例如:

function fun(str,num ,str1)表示调用函数时,应当传递两个参数,str1是本函数内部使用的局部变量,不要传递对应的参数。

区分参数和局部变量:

  • 参数提供了函数和它调用者进行数据交互的方式
  • 局部变量是临时存放数据的地方

local variables是awk实现真正局部变量的技巧,只是因为函数内部新增的变量都是全局变量,所以退而求其次将其放在参数列表上来实现局部变量。

自定义函数示例

1.一次性读取一个文件所有数据

awk 数组


特性:

  • awk的数组是关联数组(即key/value方式的数据结构),索引下标可为数值(甚至是负数、小数等),也可为字符串
    • awk数组的索引全都是字符串,即使是数值索引在使用时内部也会转换成字符串
    • awk的数组元素的顺序和元素插入时的顺序很可能是不相同的
  • awk数组支持数组的数组

访问、赋值数组元素

arr[key]
arr[key]=var

索引可以是整数、负数、0、小数、字符串。如果是数值索引,会按照CONVFMT变量指定的格式默认为%.6g先转换成字符串。

例如:

#数值索引,会按照CONVFMT变量指定的格式先转换成字符串(四舍五入)
awk 'BEGIN{arr[1.234567]="hello";print arr["1.23457"]}'  
hello
awk 'BEGIN{arr[1.234567]="hello";print arr[1.23456789]}'
hello
awk 'BEGIN{arr[1.234563]="hello";print arr[1.23456]}'
hello

通过索引的方式访问数组中不存在的元素时,会返回空字符串,同时会创建这个元素并将其值设置为空字符串

例如

awk 'BEGIN{arr[-1]=3;print length(arr);print arr[1];print arr[-1]}'
1 # arr[-1]的值
— # 此处-代表空,创建了arr[1]数组,并且设置的空值
2 #arr数组的个数,分别时arr[-1]和arr[1]


删除数组的元素

delete arr[key] :删除数组 arr[key] 元素

delete arr :删除数组所有元素

注意:删除不存在的元素不会报错

检测是否是数组

isarray(arr) 返回布尔值 可用于检测arr是否是数组,如果是数组则返回1,否则返回0。

typeof(arr) 可返回数据类型,如果arr是数组,则其返回"array

awk 'BEGIN{arr[1];print isarray(arr)}'
1

awk 'BEGIN{arr[1];print typeof(arr)}'
array

测试元素是否存在于数组当中

不要不要使用下面的方式来测试元素是否在数组中:

if(arr["i"] != ""){command}

这有会有两个问题:

  • 如果不存在arr[“i”],awk会立即创建该元素,并将其值设置为空字符串
  • 原本arr[i]数组的元素值本身就是空字符串

应当使用数组成员测试操作符in来测试:

if (i in arr){...}

#如果元素存在返回布尔真1,不存在返回布尔假0
awk 'BEGIN{arr[1]="var1";arr[2]="var2";arr["a"]="var3";print (4 in arr)}'
0
awk 'BEGIN{arr[1]="var1";arr[2]="var2";arr["a"]="var3";print (2 in arr)}'
1
awk 'BEGIN{arr[1]="var1";arr[2]="var2";arr["a"]="var3";print (1 in arr)}'
1

数组遍历

awk 中提供一种for 循环的变体方式来遍历数组

for (i in arr){print i,arr[i]} 
# i 为一个变量,arr是数组 每循环一次arr会从数组内取一个元素赋值给变量i 即i是数组的元素(下标) ,arr[i] 是元素的值,通常用来统计文件中某个单词或者字符串出现的次数,awk中通常是需要统计谁,就以谁为数组的元素(下标)

awk 'BEGIN{arr[1]="var1";arr[2]="var2";arr["a"]="var3";for(i in arr){print i,"--->"arr[i]}}'
a --->var3
1 --->var1
2 --->var2

注意 awk数组是关联数组,元素是不连续的,也就是说没有顺序。遍历awk数组时,顺序是不可预测的。

使用for循环来遍历数组的小坑

#当数组的元素为数字并且是连续的,可是用下列方式进行遍历
awk 'BEGIN{key[1]="var1";key[2]="var2";key[3]="var3";for(i=1;i<=length(key);i++){print i,"--->"key[i]}}'
1 --->var1
2 --->var2
3 --->var3

#注意 如果数组的的元素不是数字,或者不连续,用上述方法就会产生问题;  例如:
awk 'BEGIN{key[1]="var1";key[2]="var2";key["a"]="var3";for(i=1;i<=length(key);i++){print i,"--->"key[i]}}'
#会一直创建key数组的元素一直循环下去(循环中会有问题,当i=3时,会创建一个空值的arr[3],这是key的元素个数就为4,以此类推,会一直循环下去)
#因此不要随意使用 for(i=0;i<length(arr);i++) 来遍历数组,因为awk数组是关联数组。如果已经明确知道数组的所有元素索引都位于某个数值范围内,则可以使用该方式进行遍历。如果要使用此种方式一定要嵌套if语句使用!!! (其实等价于for(i in arr),还是用 for(i in arr)最方便) 例如:
awk 'BEGIN{arr[1]="var1";arr[2]="var2";arr[10]="var3";for(i=1;i<=10;i++){if(i in arr){print i,"---->"arr[i]}}}'
1 ---->var1
2 ---->var2
10 ---->var3

复合索引的数组

在awk中,很多时候单纯的一个数组只能存放两个信息:一个索引、一个值。但在一些场景下,这样简单的存储能力在处理复杂需求的时候可能会捉襟见肘。

  • 为了存储更多信息,方式之一是将第3份、第4份等信息全部以特殊方式存放到值中,例如:arr[“zhang3”]=“20:male”
  • 另一种方式是将第3份、第4份等信息存放在索引中,将多份数据组成一个整体构成单个索引。例如:arr[“name:age”]=“male”
  • 但是这样的方式在实际使用过程中并不方便,每次都需要去分割值从而取出各部分的值。
	gawk中提供了将多份数据信息组合成一个整体当作一个索引的功能。**默认方式为 arr[x,y] ,其中x和y是要结合起**

来构建成一个索引的两部分数据信息。逗号称为下标分隔符,在构建索引时会根据预定义变量SUBSEP的值将多个索

引组合起来。所以 arr[x,y] 其实完全等价于 arr[x SUBSEP y] 。

例如,如果SUBSEP设置为"@",那么 arr[5,12] = 512 存储时,其真实索引为 5@12 ,所以要访问该元素需使用 arr[“5@12”] 。

SUBSEP的默认值为 \034 ,它是一个不可打印的字符,几乎不可能会出现在字符串当中。

例如,顺时针倒转下列数据:

1 2 3 4 5 6
2 3 4 5 6 1
3 4 5 6 1 2
4 5 6 1 2 3
结果:
4 3 2 1
5 4 3 2
6 5 4 3
1 6 5 4
2 1 6 5
3 2 1 6

#代码如下
{
 nf = NF
 nr = NR
 for(i=1;i<=NF;i++){
   arr[NR,i] = $i
 }
}
END{
 for(i=1;i<=nf;i++){
   for(j=nr;j>=1;j--){
     if(j%nr == 1){
       printf "%s\n", arr[j,i]
     }else {
       printf "%s ", arr[j,i]
     }
   }
 }

子数组

子数组是指数组中的元素也是一个数组,即Array of Array,它也称为子数组(subarray)。awk也支持子数组,在效果上即是嵌套数组或多维数组。

a[1][1] = 11
a[1][2] = 12
a[1][3] = 13
a[2][1] = 21
a[2][2] = 22
a[2][3] = 23
a[2][4][1] = 241
a[2][4][2] = 242
a[2][4][1] = 241
a[2][4][3] = 243

通过如下方式遍历二维数组:

for(i in a){
   for (j in a[i]){
       if(isarray(a[i][j])){
           continue
       }
       print a[i][j]
   }
}

指定数组遍历的顺序

由于awk数组是关联数组,默认情况下, for(idx in arr) 遍历数组时顺序是不可预测的。

但是gawk提供了 PROCINFO[“sorted_in”] 来指定遍历的元素顺序。它可以设置为两种类型的值:

  • 设置为用户自定义函数
  • 设置为下面这些awk预定义好的值:
    • @unsorted :默认值,遍历时无序
    • @ind_str_asc :索引按字符串比较方式升序遍历
    • @ind_str_desc :索引按字符串比较方式降序遍历
    • @ind_num_asc :索引强制按照数值比较方式升序遍历。所以无法转换为数值的字符串索引将当作数值0进行比较
    • @ind_num_desc :索引强制按照数值比较方式降序遍历。所以无法转换为数值的字符串索引将当作数值0进行比较
    • @val_type_asc :按值升序比较,此外数值类型出现在前面,接着是字符串类型,最后是数组类(即认为 num<str<arr )
    • @val_type_desc :按值降序比较,此外数组类型出现在前面,接着是字符串类型,最后是数值型(即认为 num<str<arr )
    • @val_str_asc :按值升序比较,数值转换成字符串再比较,而数组出现在尾部(即认 str<arr )
    • @val_str_desc :按值降序比较,数值转换成字符串再比较,而数组出现在头部(即认 str<arr )
    • @val_num_asc :按值升序比较,字符串转换成数值再比较,而数组出现在尾部(即认 num<arr )
    • @val_num_desc :按值降序比较,字符串转换成数值再比较,而数组出现在头部(即认为 num<arr )
awk '
 BEGIN{
   arr[1] = "one"
   arr[2] = "two"
   arr[3] = "three"
   arr["a"] ="aa"
   arr["b"] ="bb"
   arr[10]= "ten"
   #PROCINFO["sorted_in"] = "@ind_num_asc"
   #PROCINFO["sorted_in"] = "@ind_str_asc"
   PROCINFO["sorted_in"] = "@val_str_asc"
   for(idx in arr){   
   print idx " -> " arr[idx]
   }
}'

awk 数据经典示例

  1. 去除重复行并且以指定顺序进行排序

    cat test.txt
    ID  name     gender   age    mail          tel
    1   admin     male    21   abc@qq.com     14044534012
    2   guest     female  24   def@gmail.com  15084545203
    3   Root      male    21   hhh@163.com    17048792453
    2   guest     female  24   def@gmail.com  15084545203
    4   user      male    21   www@189.com    18234532345
    5   Alex      male    13   fff@xyz.com    14646324234
    2   guest     female  24   def@gmail.com  15084545203
    6   baby      female  25   ggg@139.com    18636224672
    4   user      male    21   www@189.com    18234532345
    7   echo      female  26   kkksa@126.com  18353673544
    
    4   user      male    21   www@189.com    18234532345
    8   zhangsan  female  22   bax@hanjy.com  14723454343
    9   Steven    female  23   bc@qq.com      13463645644
    10  wangwu    male    29   bcbd@139.com   13345423453
    4   user      male    21   www@189.com    18234532345
    11  root      male    29   bcbd@139.com   13345423453
    
    #方式一,使用sort 进行排序
    awk '{arr[$0]++}END{for(i in arr){if(arr[i]==1){print i}}}'  test.txt |sort -nk1
    
    ID  name     gender   age    mail          tel
    1   admin     male    21   abc@qq.com     14044534012
    3   Root      male    21   hhh@163.com    17048792453
    5   Alex      male    13   fff@xyz.com    14646324234
    6   baby      female  25   ggg@139.com    18636224672
    7   echo      female  26   kkksa@126.com  18353673544
    8   zhangsan  female  22   bax@hanjy.com  14723454343
    9   Steven    female  23   bc@qq.com      13463645644
    10  wangwu    male    29   bcbd@139.com   13345423453
    11  root      male    29   bcbd@139.com   13345423453
    
    #方式二,使用awk中的PROCINFO["sorted_in"]进行排序
    awk 'BEGIN{PROCINFO["sorted_in"]="@ind_num_asc"}{arr[$0]++}END{for(i in arr){if(arr[i]==1){print i}}}'  test.txt 
    
    ID  name     gender   age    mail          tel
    1   admin     male    21   abc@qq.com     14044534012
    3   Root      male    21   hhh@163.com    17048792453
    5   Alex      male    13   fff@xyz.com    14646324234
    6   baby      female  25   ggg@139.com    18636224672
    7   echo      female  26   kkksa@126.com  18353673544
    8   zhangsan  female  22   bax@hanjy.com  14723454343
    9   Steven    female  23   bc@qq.com      13463645644
    10  wangwu    male    29   bcbd@139.com   13345423453
    11  root      male    29   bcbd@139.com   13345423453
    
    #方式三 在输出时保持文件顺序不变,此次就不可以使用for循环,让文件每读取一行打印一行 arr[$0]++是先返回arr[$0]的值后在进行运算 例如因此当第一次执行arr[admin]返回值为0 取反后为1即布尔真,执行打印第一次,当再次读到arr[admin]时返回值为真,取反为假。不执行打印
    awk '!(arr[$0]++){print $0}' test.txt
    ID  name     gender   age    mail          tel
    1   admin     male    21   abc@qq.com     14044534012
    2   guest     female  24   def@gmail.com  15084545203
    3   Root      male    21   hhh@163.com    17048792453
    4   user      male    21   www@189.com    18234532345
    5   Alex      male    13   fff@xyz.com    14646324234
    6   baby      female  25   ggg@139.com    18636224672
    7   echo      female  26   kkksa@126.com  18353673544
    
    8   zhangsan  female  22   bax@hanjy.com  14723454343
    9   Steven    female  23   bc@qq.com      13463645644
    10  wangwu    male    29   bcbd@139.com   13345423453
    11  root      male    29   bcbd@139.com   13345423453
    
    
  2. 统计行出现次数

    #还是以test.txt为例,打印出不重复的行和对应每行出现的次数并排序
    awk 'BEGIN{PROCINFO["sorted_in"]="@ind_num_asc"}{arr[$0]++}END{for(i in arr){print i,"-->"arr[i]}}'  test.txt 
     -->1
    ID  name     gender   age    mail          tel -->1
    1   admin     male    21   abc@qq.com     14044534012 -->1
    2   guest     female  24   def@gmail.com  15084545203 -->3
    3   Root      male    21   hhh@163.com    17048792453 -->1
    4   user      male    21   www@189.com    18234532345 -->4
    5   Alex      male    13   fff@xyz.com    14646324234 -->1
    6   baby      female  25   ggg@139.com    18636224672 -->1
    7   echo      female  26   kkksa@126.com  18353673544 -->1
    8   zhangsan  female  22   bax@hanjy.com  14723454343 -->1
    9   Steven    female  23   bc@qq.com      13463645644 -->1
    10  wangwu    male    29   bcbd@139.com   13345423453 -->1
    11  root      male    29   bcbd@139.com   13345423453 -->1
    
  3. 统计test.txt文件中字母出现的次数

    awk 'BEGIN{FPAT="[a-z]|[A-Z]"}{for (j=1;j<=NF;j++){arr[$j]++}}END{for (i in arr){print i,arr[i]}}' test.txt|sort -rnk2
    e 42
    m 38
    a 31
    l 22
    ...
    I 1
    D 1
    A 1
    ...
    

  1. 统计tcp ipv4连接状态次数

    ss -ant4|awk 'NR>1{arr[$1]++}END{for (i in arr){print arr[i],":"i }}'
    9 :LISTEN
    11 :ESTAB
    1 :TIME-WAIT
    
  2. 将test1.txt文件中各个字母所对应的数值最大的打印出来

    cat test1.txt 
    
    a 10
    b 11
    c 12
    d 19
    a 15
    e 17
    f 20
    g 14
    g 18
    f 16
    b 13
    
    awk '{arr[$1]= (arr[$1]<$2) ? $2 : arr[$1] }END{for (i in arr){print i,arr[i]}}' test1.txt 
    a 15
    b 13
    c 12
    d 19
    e 17
    f 20
    g 18
    
    awk '{arr[$1]=$2;if(arr[$1]<$2){arr[$1]=$2}else{arr[$1]=arr[$1]}}END{for (i in arr){print i,arr[i]}}' test1.txt 
    a 15
    b 13
    c 12
    d 19
    e 17
    f 16
    g 18