玩转gawk
玩转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部分
- 都将设置NR、FNR、RT、
- 执行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(一堆小数位)
算数运算符
++ – 自增、自减,支持i和i或–i或i–
^ 幂运算(**也用于幂运算)
+ - 一元运算符(正负数符号)
* / % 乘除取模运算
+ - 加减法运算
+= -= 赋值运算
#注意 优先级从上到下 +±-最高 +=最低
- ++和–既可以当作独立语句,也可以作为表达式,但是要注意
++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
- ^幂运算是从右向左计算的,例如:
[root@sea awk]#: awk 'BEGIN{print 2^3^2}'
512
#会先计算3的2次方(9) 再运算2的9次方 为512
-
赋值操作(优先级最低):
= += -= *= /= %= ^= **=
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"
:一次性读取所有数据,但有些特殊文件中包含了空字符 \0RS="^$"
:真正的一次性读取所有数据,因为非空文件不可能匹配成功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进程工作时的状态信息。
如果:
- PROCINFO[“FS”]==“FS” ,表示使用FS分割获取字段
- PROCINFO[“FPAT”]==“FPAT” ,表示使用FPAT匹配获取字段
- 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)。
修改 $0 ,将使用 FS 重新分割字段,所以会影响 $1、$2…
echo hello world |awk 'BEGIN{$0="HELLO WORLD" ;print $1,$2}' #结果如下 HELLO WORLD
修改 $1、$2 ,将根据 $1 到 $NF 来重新计算 $0(即使用OFS重建$0),即使是 $1 = $1 这样的原值不变的修改,也一样会重新计算 $0
echo "hello world" |awk 'BEGIN{OFS="--"}{$1="HELLO";print $0}' #结果如下: HELLO--world
为不存在的字段赋值,将新增字段并按需使用空字符串填充中间的字段,并使用 OFS 重新计算 $0
echo "hello world" |awk 'BEGIN{OFS="--"}{$4="HELLO";print $0}' #结果如下: hello--world----HELLO #因字段中没有$3但我们对$4进行了赋值,因此新增的字段使用空字符串填充中间的字段
增加NF值,将使用空字符串新增字段,并使用 OFS 重新计算 $0
echo "hello world HELLO WORLD " |awk 'BEGIN{OFS="--"}{NF=(NF+2);print $0}' #结果如下: hello--world--HELLO--WORLD---- #注意尾部出现了四个短横线
减小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()的返回值是命令的退出状态码 |
输出 | ||
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
逗号分隔要打印的字段列表,各字段都会自动转换成字符串格式,然后通过预定义变量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 “%2s”,“world”,"hello"输出hello world
- Ns %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 数据经典示例
-
去除重复行并且以指定顺序进行排序
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
-
统计行出现次数
#还是以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
-
统计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 ...
-
统计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
-
将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