linux 三剑客之一 awk 的详细介绍
awk英文文档
https://www.gnu.org/software/gawk/manual/gawk.html
基本组成
基本组成形式
awk options program file1
awk options program file1 file2
awk options program # 进入交互模式,直到ctrl+d结束输入
由于gawk命令行假定脚本是单个文本字符串,你必须将脚本放到单引号中。
awk 'BEGIN{ print "start";command2 } pattern { command1;command2 } END{ print "end";command2 }' file
- 一个命令就是一个以新行或者分号分隔的语句序列。
- 由于模式和命令两者任一都是可选的,所以需要使用大括号包围动作以区分于其他模式。
- 多条语句之间用
;
分隔awk 'BEGIN {FS=":"} { "grep root /etc/passwd" | getline; print $1,$6 }'
运行流程
awk以逐行的形式处理文件
BEGIN
之后的命令会先于公共语句块执行BEGIN语句块在awk开始从输入流中读取行之前被执行(除非调用了
getline
)。这是一个可选的语句块,诸如变量初始化、打印输出表格的表头等语句通常都可以放在BEGIN语句块中。对于匹配PATTERN的行,awk会对其执行
PATTERN
之后的命令这个语句块是可选的。如果不提供,则默认执行
{ print }
,即打印所读取到的每一行。 如果提供,则每个pattern依次测试每个输入行。对于匹配到行的模式,其对应的命令(也许包含多步)得到执行,然后读取下一行并继续匹配,直到所有的输入读取完毕。最后,在处理完整个文件之后,awk会执行
END
之后的命令END语句块和BEGIN语句块类,也是可选的语句块。它在awk读取完输入流中所有的行之后被执行。像打印所有行的分析结果这种常见任务都是在END语句块中实现的。
输出
print
输出
print
接受的变量之间以逗号分隔,在输出时会以空格作为变量之间的分隔符。
awk '{print $3,$4}' marks.txt
print
之间的空格不会影响输出格式
awk '{print $3 "\t" $4}' marks.txt # $3与\t之间的空格不会显示在输出中
awk '{print $3 , $4}' marks.txt # 逗号表示最终会以空格分分隔两个字段
字符串拼接
在awk的print
语句中,双引号被当作拼接操作符(concatenation operator)使用。
echo | awk '{ var1="v1"; var2="v2"; var3="v3"; \
print var1 "-" var2 "-" var3 ; }'
# v1-v2-v3
省略print
如果没有主体块——默认的动作是输出行。因此比如搜索字符串Tom
可以使用下面简略方式实现:
awk '/Tom/' marks.txt
打印一行
{ print }
# 或者,由于 $0 表示整行,
{ print $0 }
打印特定列
使用一个 print 语句可以在同一行中输出不止一个字段. 下面的程序输出了每 行输入中的第一和第三个字段
{ print $1, $3 }
NF 列总数
该变量表示一共有多少列
{ print NF, $1, $NF } # 打印第一列 和最后一列
计算和打印
你也可以对字段的值进行计算后再打印出来. 下面的程序
{ print $1, $2 * $3 }
NR 打印行号
存储当前已经读取了多少行的计数。给每一行加上行号:
{ print NR, $0 }
在输出中添加内容
双引号内的文字将会在字段和计算的值中插入输出.
{ print "total pay for", $1, "is", $2 * $3 }
排序输出
最简单的方式是使用awk将每位员工的总薪酬置于其记录之前,然后利用sort命令来处理awk的输出。Unix上,命令行如下:
awk '{ printf("%6.2f %s\n", $2 * $3, $0) }' emp.data | sort
打印表头,表尾
BEGIN 用于匹配第一个输入文件的第一行之前的位置, END 则用于匹配处理过的最后一个文件的最后一行之后的位置
awk 'BEGIN {print "Name Rate Hour";print "========="} {print $0}' file
字符串拼接
- 在命令中使用空格进行变量拼接
- 在
print
语句块中使用双引号拼接# 将names变量与当前行的的$1以及空格进行拼接,赋值给names { names = names $1 " "} END { print names }
只打印最后一行
打印最后一个输入行
虽然在 END 动作中 NR 还保留着它的值,但 $0 没有。程序是打印最后一个输入行的一种方式
{ last = $0 }
END { print last }
printf
高级输出
基本用法
printf 语句的形式如下:
printf(format, value1, value2, ..., valuen)
其中 format 是字符串,包含要逐字打印的文本,穿插着 format 之后的每个值该如何打印的规格(specification)。一个规格是一个 % 符,后面跟着一些字符,用来控制一个 value 的格式。第一个规格说明如何打印 value1 ,第二个说明如何打印 value2 ,… 。因此,有多少 value 要打印,在 format 中就要有多少个 % 规格。(与C语言很像)
这里有个程序使用 printf 打印每位员工的总薪酬::
{ printf("total pay for %s is $%.2f\n", $1, $2 * $3) }
转义序列
退格符\b
换页符\f
换行符\n
,进入下一行
回车符\r
,光标移动到第一列
这与以前的打字机相关,以前的打字机达打完一行后,机械上需要2步,先将指针移动到下一行(换行),然后将指针回到行首(回车)
我们在每个域输出后输出一个回车符\r
,随后输出的域会覆盖之前输出的内容。也就是说,我们只能看到最后输出的 Field 4。
awk 'BEGIN { printf "Field 1\rField 2\rField 3\rField 4\n" }'
# Field 4
垂直制表符
如下示例,使用垂直制表符输出不同域:
awk 'BEGIN { printf "Sr No\vName\vSub\vMarks\n" }'
# 执行上面的命令可以得到如下的结果
Sr No
Name
Sub
Marks
格式说明符%
符号 | 说明 |
---|---|
%c | 输出单个字符。如果参数是个数值,那么数值也会被当作字符然后输出。如果参数是字符串,那么只会输出第一个字符。 |
%d 与 %i | 输出十进制数的整数部分。 |
%f | 输出浮点数,以 [-]ddd.dddddd 的格式 |
%e 与 %E | 输出浮点数,以 [-]d.dddddde[+-]dd 的格式。区别就是输出8.066000e+01 与8.066000E+01 的e 的大小写 |
%g 与 %G | 输出浮点数,使用 %e 或 %E 转换。但它们会删除那些对数值无影响的 0。(推荐) |
%o | 无符号八进制输出。 |
%u | 无符号十进制数输出。 |
%x 与 %X | 输出十六进制无符号数。%X 中使用大写字母,%x 使用小写字母。 |
%% | 输出百分号(%),不需要输入参数。 |
格式说明符% 的可选参数
列宽
%8d
表示用空格填充占8列,如果紧接在 % 后是以0开头的数字%08d
,表示输出应该使用0填充而不是空格字符。
对齐
默认是右边对齐,在 % 之后数字之前使用减号(-)即可指定输出左对齐
符号前缀
但是+
不是表示右对齐,是表示输出数值的符号,正号也输出。
awk 'BEGIN { num = -10; printf "Num = %-+10d\n", num }' | cat -vte
哈希#
将#
放在%
后, 可以为 %o 的结果前添加0,为 %x 或 %X 输出的结果前添加 0x 或 0X (结果不为零时),为 %e,%E,%f,%F添加小数点;对于 %g 或 %G,使用哈希可以保留尾部的零
awk 'BEGIN { printf "Octal representation = %#o\nHexadecimal representaion = %#X\n", 10, 10}'
重定向、管道、双向管道
重定向
AWK 重定向操作符重定向数据到文件 message.txt 中。
awk 'BEGIN { print "Hello, World !!!" > "/tmp/message.txt" }'
cat /tmp/message.txt
管道
下面的例子中我们使用 tr
命令将小写字母转换成大写。
awk 'BEGIN { print "hello, world !!!" | "tr [a-z] [A-Z]" }'
# HELLO, WORLD !!!
双向通信通道
(TODO 还不知道怎么用)
AWK 允许使用 |&
与一个外部进程通信,并且可以双向通信。下面的例子中,使用 tr
命令将字母转换为大写字母。
BEGIN {
cmd = "tr [a-z] [A-Z]"
print "hello, world !!!" |& cmd
close(cmd, "to")
cmd |& getline out
print out;
close(cmd);
}
执行上面的命令可以得到如下的结果:
HELLO, WORLD !!!
脚本的内容看上去很神秘吗?让我们一步一步揭开它神秘的面纱。
- 第一条语句 cmd = “tr [a-z][A-Z]” 在AWK 中建立了一个双向的通信通道。
- 第二条语句 print 为 tr 命令提供输入。&| 表示双向通信。
- 第三条语句 close(cmd, “to”) 执行后关闭 to 进程。
- 第四条语句 cmd |& getline out 使用 getline 函数将输出存储到 out 变量中。
- 接下来的输出语句打印输出的内容,最后 close 函数关闭 cmd。
保存到不同文件中
跳过表头,第6列是文件名,因此这样就可以将每一行分别保存在不同的文件中,进行了分类
awk 'NR!=1{print $4,$5 > $6}' netstat.txt
常用参数
参数 | 说明 |
---|---|
标准参数 | |
-F fs | fs是分隔符,默认是空白符(空格、制表符),fs可以是字符串或正则表达式。 如 -F: 表示冒号为分隔符;如果想指定空格为分隔符,请看下面例子。 |
-v var=value | 赋值一个用户定义变量,将外部变量传递给awk |
-f scripfile | 从脚本文件中读取awk命令 |
-p[file] | 用于格式化 awk 脚本文件。默认输出文件是 awkprof.out。 |
gawk 参数 | |
-mf N | 指定要处理的数据文件中的最大字段数 |
-mr N | 指定数据文件中的最大数据行数 |
在BEGIN语句块中可以用OFS=”delimiter”设置输出字段分隔符。
awk 'BEGIN { FS=":" }{ print }
指定空格为分隔符
echo -e "a\tb\nc d"|awk -F' ' '{print $2}' # 无效,还是会将\t进行分割
echo -e "a\tb\nc d"|awk -F'[ ]' '{print $2}' # 推荐
同时使用多个分隔符
# 使用多个分隔符, 遇到";"或者","就进行分割,因此下面的例子会得到 5 列
echo "1;2,3;4,5"|awk -F '[;,]' '{print $1,$2,$3,$4,$5}'
# 1 2 3 4 5
或者使用|
awk -F"\t|," '' file # 以\t 或者逗号分隔
格式化脚本
[jerry]$ awk --profile 'BEGIN{printf"---|Header|--\n"} {print} END{printf"---|Footer|---\n"}' marks.txt > /dev/null
[jerry]$ cat awkprof.out
执行上面的命令可以得到如下的结果:
\# gawk profile, created Sun Oct 26 19:50:48 2014
# BEGIN block(s)
BEGIN {
printf "---|Header|--\n"
}
# Rule(s)
{
print $0
}
# END block(s)
END {
printf "---|Footer|---\n"
}
模式(pattern)种类
BEGIN { 语句 }
在读取任何输入前执行一次语句
END { 语句 }
读取所有输入之后执行一次语句
表达式 { 语句 }
对于表达式为真(即,非零或非空)的行,执行语句
/正则表达式/ { 语句 }
如果输入行包含字符串与正则表达式相匹配,则执行语句
组合模式 { 语句 }
一个 组合模式 通过与(&&
),或(||
),非(!
),以及括弧来组合多个表达式;对于组合模式为真的每个输入行,执行 语句
模式1,模式2 { 语句 }
范围模式(range pattern)匹配从与模式1相匹配的行到与模式2相匹配的行(包含该行)之间的所有行,对于这些输入行,执行语句。
BEGIN和END不与其他模式组合。范围模式不可以是任何其他模式的一部分。BEGIN和END是仅有的必须搭配动作的模式。
例子:
awk 'NR < 5' # 行号小于5的行
awk 'NR==1,NR==4' # 行号在1到5之间的行
awk '/linux/' # 包含模式为linux的行(可以用正则表达式来指定模式)
awk '!/linux/' # 不包含模式为linux的行
选择、过滤
数值判断
$2 * $3 > 50 { printf("$%.2f for %s\n", $2 * $3, $1) }
文本内容选择,支持正则
$1 == "Susie" { print } # 比较字段是否等于字符串时,要使用双引号包起来
$1~/正则内容/ { print $1} # 对某一列进行正则
/正则内容/ { print $1} # 对整行进行匹配
/条件1/,/条件2/ { print $1} # 对整行进行多条件匹配
匹配中文
awk '$1 ~/[一-龥]/' 匹配中文
awk '$1 ~/[\u4e00-\u9fa5]/' 这样反而不行,要不unicode转成对应的中文
多条件组合
逻辑操作符与 && , 或 || , 以及非 ! 对模式进行组合
$2 >= 4 || $3 >= 20 {print}
注意:!
是在括号前的,第1种其实与第2种一模一样,正确的是第3种
awk -F"\t" 'BEGIN{while("cat file"|getline ){dict[c]=1}} ! $2 in dict{print}' some.txt |wc
0 0 0
awk -F"\t" 'BEGIN{while("cat file"|getline c){dict[c]=1}} (! $2 in dict){print}' some.txt |wc
0 0 0
awk -F"\t" 'BEGIN{while("cat file"|getline ){dict[c]=1}} !($2in dict){print}' some.txt |wc
1296838 14273901 226060062
多条命令
要在命令行上的程序脚本中使用多条命令,
- 只要在命令之间放个分号即可。
$ echo "My name is Rich" | gawk '{$4="Christine"; print $0}' My name is Christine
- 也可以用次提示符一次一行地输入程序脚本命令。
awk '{ \
> $4="Christine"
> print $0}'
My name is Rich
$ My name is Christine
将命令保存在文件中
当引号内的程序过长时,可以单独保存在文件中,假设存在文件 progfile ,输入命令行:
awk -f progfile file
文件中不用引号包起来,但是中括号还是需要的,多条指令在同一行中用分号分分隔,或者以多行形式书写
读取命令的输出
awk可以调用命令并读取输出。把命令放入引号中,然后利用管道将命令输出传入getline:"command" | getline output ;
下面的代码从/etc/passwd
文件中读入一行,然后显示出用户登录名及其主目录。在BEGIN语 句块中将字段分隔符设置为:,在主语句块中调用了grep
。
awk 'BEGIN {FS=":"} { "grep root /etc/passwd" | getline; print $1,$6 }'
# root /root
变量
内置变量
变量 | 说明 |
---|---|
标准 AWK 变量 | |
ARGC | 命令行提供的参数的个数 |
ARGV | 存储命令行输入参数的数组。数组的有效索引是从 0 到 ARGC-1。 |
ARGIND | 命令行中当前文件的位置(从0开始算) |
CONVFMT | 数据转换为字符串的格式,其默认值为 %.6g |
OFMT | 数值输出的格式,它的默认值为 %.6g。 |
OFS | 输出域之间的分割符,其默认为空格。 |
ORS | 输出记录(行)之间的分割符,其默认值是换行符。 |
RS | 输入记录的分割符,其默认值为换行符。 |
NR | 表示记录编号,当awk将行作为记录时,该变量相当于当前行号。全局行数(第二个文件的第一行接着第一个文件尾行数顺序计数) |
FNR | 当前文件自身的行数(不考虑前几个输入文件的自身行数及总数。NR 的值依次为:1,2……40,41,42……90。FNR的值依次为:1,2……40, 1, 2……50 |
NF | 表示字段数量,在处理当前记录时,相当于字段数量。默认的字段分隔符是空格。 |
FS | 定义分隔符,可以用在 BEGIN 语句块中,这样你就不用依靠脚本用户在命令行选项中定义字段分隔符了。 |
$0 | 该变量包含当前记录的文本内容。 |
$n | 该变量包含第n个字段的文本内容。 |
ENVIRON | 与环境变量相关的关联数组变量,awk 'BEGIN { print ENVIRON["USER"] }' |
FILENAME | 此变量表示当前文件名称 |
RLENGTH | 表示 match 函数匹配的字符串长度 |
RSTART | 表示由 match 函数匹配的字符串的第一个字符的位置 |
GNU AWK 特定的变量 | |
IGNORECASE | GAWK将变得大小写不敏感,awk 'BEGIN{IGNORECASE=1} /amit/' marks.txt |
自定义变量
定义变量
变量不用初始化即可使用
数值
用作数字的awk变量的默认初始值为0,所以我们不需要初始化 emp 。
这个程序使用一个变量 emp 来统计工作超过15个小时的员工的数目:
$3 > 15 { emp = emp + 1 }
END { print emp, "employees worked more than 15 hours" }
对于第三个字段超过15的每行, emp 的前一个值加1。该程序输出:
3 employees worked more than 15 hours
字符串
awk变量可以保存数字也可以保存字符串。这个程序会找出时薪最高的员工:
# 如果$2大于最大值,则更新为当前行的信息
$2 > maxrate { maxrate = $2; maxemp = $1 }
END { print "highest hourly rate:", maxrate, "for", maxemp }
数组变量
awk 中其实并不存在数组类型,所谓的关联数组是一种使用字符串作为索引的字典。因此并不是 ["value1", "value2"]
这样,而是{0: "value1", 1: "value2"}
。在awk中数组之间是无序的,一个数组的key值是数值,例如1,2,3,并不代表该数组元素在数组中的出现的位置。
awk 'BEGIN {FS=":"} {nam[$1]=$5} END {for (i in nam) {print i,nam[i]}}' /etc/passwd
# root root
# ftp FTP User
# userj Joe User
awk没有not in语法
请使用下面的用法
!($1 in dict) # 通用情况
!dict[$1] # 适用于判断在不在字典里进行过滤的情况
http://blog.chinaunix.net/uid-10540984-id-323936.html
判断一个元素是否在数组中
awk 支持in
操作,但是in
判断的是键 key
,awk 自身是没有判断是否在值value
中的方法。
因此为了实现判断是否在数组中,要将value
转为key
,
BEGIN {
split("value1 value2", valuesAsValues)
# valuesAsValues = {0: "value1", 1: "value2"}
for (i in valuesAsValues) valuesAsKeys[valuesAsValues[i]] = ""
# valuesAsKeys = {"value1": "", "value2": ""}
}
# Now you can use `in`
($1 in valuesAsKeys) {print}
一行的写法
echo "A:B:C:D:E:F" | tr ':' '\n' | \
awk 'BEGIN{ split("A D F", parts); for (i in parts) dict[parts[i]]=""} $1 in dict'
遍历数组
awk支持列表形式的for
循环,是对 key 进行遍历,然后显示出数组的内容:
for(k in array) { print array[k]; }
数组排序
使用 asort 完成数组元素的排序,或者使用 asorti 实现数组索引的排序等等
fruits["mango"]="yellow";
fruits[mango]="yellow"; # 这种写法也可以
value必须加双引号
delete array_name[index]
删除数组元素
delete arr[key]
例子:按行逆序打印
第一个动作将输入行存为数组 line 的连续元素;即第一行放在 line[1] ,第二行放在 line[2] , 依次继续。 END 动作使用一个 while 语句从后往前打印数组中的输入行:
# 反转 - 按行逆序打印输入
{ line[NR] = $0 } # 记下每个输入行
END { i = NR # 逆序打印
while (i > 0) {
print line[i]
i = i - 1
}
}
以 emp.data 为输入,输出为
Susie 4.25 18
Mary 5.50 22
Mark 5.00 20
Kathy 4.00 10
Dan 3.75 0
Beth 4.00 0
外部变量
方法一(推荐):
借助选项-v
,我们可以将一个外部值(并非来自stdin)传递给awk:
VAR=10000
echo | awk -v VARIABLE=$VAR '{ print VARIABLE }' [filename]
# 10000
gawk 程序在引用变量值时并未像shell脚本一样使用美元符。
可以不用加引号
echo | awk -v name="Jerry" '{print name}'
echo | awk -v name=Jerry '{print name}' # 等价
echo | awk -v path=$PATH '{print path}' # 如果是环境变量,需要添加$
可以将多个外部变量传递给awk。例如:
var1="Variable1"
var2="Variable2"
echo | awk '{ print v1,v2 }' v1=$var1 v2=$var2
# Variable1 Variable2
在上面的方法中,变量以键值对的形式给出,使用空格分隔(v1=$var1 v2=$var2
),作为awk 的命令行参数紧随在BEGIN
、{}
和END
语句块之后。
方法二:用引号括起来直接用,是"'$var'"
的形式。
var="abc"
awk 'BEGIN{print "'$var'"}'
方法三:方法二类似,但使用"'"
把shell变量包起来,即"'"$var"'"
如果变量的值中包含空格,为了shell不把空格作为分隔符,则应使用方法二。
var="this a test"
awk 'BEGIN{print "'"$var"'"}'
方法四:export变量,然后在awk中使用ENVIRON["var"]
形式(大写)获取环境变量的值
var="this a test"; export var;
awk 'BEGIN{print ENVIRON["var"]}'
操作符
https://doc.yonyoucloud.com/doc/wiki/project/awk/operators.html
基本与C语言的操作相同
运算符 | 描述 | ||
---|---|---|---|
= += -= *= /= %= ^= **= | 赋值 | ||
?: | C条件表达式 | ||
\ | \ | 逻辑或 | |
&& | 逻辑与 | ||
~ ~! | 匹配正则表达式和不匹配正则表达式 | ||
< <= > >= != == | 关系运算符 | ||
空格 | 拼接字符串 | ||
+ – | 加,减 | ||
* / % | 乘,除与求余 | ||
+ – ! | 一元加,减和逻辑非 | ||
^ *** | 求幂 | ||
++ — | 增加或减少,作为前缀或后缀 | ||
$ | 字段引用 | ||
in | 字典成员 |
+=
, 等于==
,不等于!=
逻辑与运算符为 &&
。逻辑或运算符为 ||
。逻辑非 !
。
逻辑非将 expr1 的真值取反。如果 expr1 为真,则返回 0。否则返回 1。下面的示例判断字符串是否为空:
[jerry]$ awk 'BEGIN { name = ""; if (! length(name)) print "name is empty string." }'
三元运算符
[jerry]$ awk 'BEGIN { a = 10; b = 20; (a > b) ? max = a : max = b; print "Max =", max}'
字符串连接操作符
空格 (space) 操作符可以完成两个字符串的连接操作。示例如下:
[jerry]$ awk 'BEGIN { str1="Hello, "; str2="World"; str3 = str1 str2; print str3 }'
Hello, World
数组成员操作符
数组成员操作符为 in。该操作符用于访问数组元素 。下面的示例用于此操作符输出数组中所有元素。
[jerry]$ awk 'BEGIN { arr[0] = 1; arr[1] = 2; arr[2] = 3; for (i in arr) printf "arr[%d] = %d\n", i, arr[i] }'
arr[0] = 1
arr[1] = 2
arr[2] = 3
awk中并没有数组对象,其实是字典,因此i
遍历的是arr的key,而不是value
匹配(Match)
匹配运算符为 ~。不匹配操作符为 !~。 它用于搜索包含匹配模式字符串的域。下面的示例中将输出包括 9 的行:
[jerry]$ awk '$0 ~ 9' marks.txt # 这种写法只对数字有效,如果是字母,就会失效
[jerry]$ awk '$0 ~ /9/' marks.txt # 推荐正则写法
控制语句
仅可以在动作中使用
一定要加{}
与其他语言一样的用法,括号内的为同一语句块
if
if (condition)
{
action-1
action-1
.
.
action-n
}
else if (a == 30)
print "a = 30";
else
print "a = 30";
例子:将每隔几行就拼接在一起输出
echo -e "cat\nbat\nfun\nfin\nfan\nend" | awk '{str=str $0};NR %2==0{print str;str=""}'
if-else 语句中,if 后的条件会被计算。如果为真,执行第一个 print 语句。否则,执行第二个 print 语句。注意我们可以使用一个逗号将一个长语句截断为多行来书写。
while
一个 while 语句有一个条件和一个执行体。条件为真时执行体中的语句会被重复执行。这个程序使用公式 value=amount(1+rate)yearsvalue=amount(1+rate)years
来演示以特定的利率投资一定量的钱,其数值是如何随着年数增长的。
# interest1 - 计算复利
# 输入: 钱数 利率 年数
# 输出: 复利值
{ i = 1
while (i <= $3) {
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
i = i + 1
}
}
条件是 while 后括弧包围的表达式;循环体是条件后大括号包围的两个表达式。 printf 规格字符串中的 \t 代表制表符; ^ 是指数操作符。从 # 开始到行尾的文本是注释,会被awk忽略,但能帮助程序的读者理解程序做的事情。
你可以为这程序输入三个一组的数字,看看不一样的钱数、利率、以及年数会产生什么。例如,如下事务演示了1000美元,利率为6%与12%,5年的复利分别是如何增长的::
$ awk -f interest1
1000 .06 5
1060.00
1123.60
1191.02
1262.48
1338.23
1000 .12 5
1120.00
1254.40
1404.93
1573.52
1762.34
do -while
awk 'BEGIN {i = 1; do { print i; ++i } while (i < 6) }'
for
for ,将大多数循环都包含的初始化、测试、以及自增压缩成一行。
{ for (i = 1; i <= $3; ++i)
printf("\t%.2f\n", $1 * (1 + $2) ^ i)
}
初始化 i = 1 只执行一次。接下来,测试条件 i <= $3 ;如果为真,则执行循环体的 printf 语句。循环体执行结束后执行自增 i = i + 1 ,接着由另一次条件测试开始下一个循环迭代。代码更加紧凑,并且由于循环体仅是一条语句,所以不需要大括号来包围它。
跳出循环(break、continue、exit、next)
break、continue、exit都支持
next
等价于continue
,next 停止处理当前记录,并且进入到下一条记录的处理过程。下面的例子中,当模式串匹配成功后程序并不执行任何操作:
awk '{if ($0 ~/Shyam/) next; print $0}' marks.txt
nextfile
nextfile 停止处理当前文件,从下一个文件第一个记录开始处理。
awk '{ if ($0 ~ /file1:str2/) nextfile; print $0 }' file1.txt file2.txt
内置函数
字符串相关
match
返回正则表达式在字符串 str 中第一个最长匹配的位置。如果能够找到,返回非0值;否则,返回0。match()有两个相关的特殊变量,分别是RSTART
和RLENGTH
。变量RSTART
包含了匹配内容的起始位置,而变量RLENGTH
包含了匹配内容的长度。
$ awk 'BEGIN {
str = "One Two Three"
subs = "Two"
ret = match(str, subs)
printf "Substring \"%s\" found at %d location.\n", subs, ret
}'
# Substring "Two" found at 5 location.
asort 数组排序
asort(arr,[, d [,how] ])
asort 函数使用 GAWK 值比较的一般规则排序 arr 中的value,然后用以 1 开始的有序整数替换排序内容的索引。
大写的会排在前面
awk 'BEGIN {
arr[0] = "Three"
arr[1] = "One"
arr[2] = "two"
print "Array elements before sorting:"
for (i in arr) {
print arr[i]
}
asort(arr)
print "Array elements after sorting:"
for (i in arr) {
print arr[i]
}
}'
asorti 数组排序
asorti(arr,[, d [,how] ])
asorti 函数的行为与 asort 函数的行为很相似,二者的差别在于 aosrt 对数组的value排序,而 asorti 对数组的index排序。
[jerry]$ awk 'BEGIN {
arr["Two"] = 1
arr["One"] = 2
arr["Three"] = 3
asorti(arr)
print "Array indices after sorting:"
for (i in arr) {
print arr[i]
}
}'
sub、gsub 替换
sub
将正则表达式regex匹配到的第一处内容替换成replacment_str。
gsub(regx,sub, string)
gsub 是全局替换( global substitution )的缩写。它将出现的子串(sub)替换为 regx。第三个参数 string 是可选的,默认值为 $0,表示在整个输入记录中搜索子串。进行一次替换的有sub(regex,sub,string)函数
$ awk 'BEGIN {
str = "Hello, World"
print "String before replacement = " str
gsub("World", "Jerry", str)
print "String after replacement = " str
}'
index 判断子串包含
index(str,sub)
index 函数用于检测字符串 sub 是否是 str 的子串。如果 sub 是 str 的子串,则返回子串 sub 在字符串 str 的开始位置;若不是其子串,则返回 0。str 的字符位置索引从 1 开始计数。
$ awk 'BEGIN {
str = "One Two Three"
subs = "Two"
ret = index(str, subs)
printf "Substring \"%s\" found at %d location.\n", subs, ret
}'
split 分割字符串
split(str, arr, regex)
split 函数使用正则表达式 regex 分割字符串 str。分割后的所有结果存储在数组 arr 中。如果没有指定 regex 则使用 FS 切分。
[jerry]$ awk 'BEGIN {
str = "One,Two,Three,Four"
split(str, arr, ",")
print "Array contains following values"
for (i in arr) {
print arr[i]
}
}'
substr 字符串截取
substr(str, start, length)
substr 函数返回 str 字符串中从第 start 个字符开始长度为 length 的子串。如果没有指定 length 的值,返回 str 从第 start 个字符开始的后缀子串。
$ awk 'BEGIN {
str = "Hello, World !!!"
subs = substr(str, 1, 5)
print "Substring = " subs
}'
# Substring = Hello
strtonum字符串转数字
strtonum(str)
strtonum 将字符串 str 转换为数值。 如果字符串以 0 开始,则将其当作八进制数;如果字符串以 0x 或 0X 开始,则将其当作十六进制数;否则,将其当作浮点数。
[jerry]$ awk 'BEGIN {
print "Decimal num = " strtonum("123")
print "Octal num = " strtonum("0123")
print "Hexadecimal num = " strtonum("0x123")
}'
Decimal num = 123
Octal num = 83
Hexadecimal num = 291
大小写转换
tolower(str)
、toupper(str)
将字符串 str 进行大小写字母转换,然后返回。注意,字符串 str 本身不被改变。
getline
getline:得到行,但是注意,得到的并不是当前行,而是当前行的下一行。原因如下:
从整体上来说,应这么理解它的用法:
getline var
读取一行内容给var
,没有变量时等价于getline $0
- 当其左右无重定向符 | 或 < 时,getline作用于当前文件,读入当前文件的一行给其后跟的变量
var
或$0
(无变量时);由于awk
在处理getline
之前已经读入了一行,所以getline
得的是下一行(第二行开始)。 当其左右有重定向符 | 或 < 时,
getline
则作用于定向输入文件(用重定向符从另外一个文件中读取内容、管道符号获得UNIX命令的输出)赋值给$0
或getline
后面的变量。由于该文件是刚打开,并没有被awk
读入一行,那么getline
返回的是该文件的第一行,而不是隔行,并赋值。getline
会记住重定向的文件读取到哪一行了,并不会重复执行getline var < file
重复读取文件,而是一次读取一行getline
执行后会更新NF
,NR
,FNR
等这些内部变量。
例子:
比较 getline
后有无变量的区别
# 显示奇数
$ seq 10 | awk '{getline var; print $0}' # getline var 后,内部变量不变,因此$0仍然是第1行
1
3
5
7
9
# 显示偶数
$ seq 10 | awk '{getline; print $0}' # getline 等价于 getline $0 ,处理getline之前$0是第一行,因此$0变为第2行
2
4
6
8
10
同时读取2个文件,不用担心getline < "b.txt"
重复读取文件,
$ awk '{printf "%s ", $0; getline < "b.txt"; print $0}' a.txt
# 记住 getline 等价于 getline $0 ,因此$0变成了 b 文件的内容
1 6
2 7
3 8
4 9
5 10
# 交替打印
awk '{getline var < "a.txt";print var;print $0}' b.txt # var 是文件a的行,$0是文件b的内容
获取命令输出
$ awk 'BEGIN {"date" | getline; close("date"); print $0}'
Tue May 10 07:50:51 PDT 2016
注意,使用管道符号时,getline
中这样是无法获取环境变量FILE
的
awk -F"\t" 'BEGIN{while("cat $FILE"|getline c){dict[c]=1}} ($2 in dict){print}' file
自定义函数
function find_min(num1, num2)
{
if (num1 < num2)
return num1
return num2
}
# Script execution starts here
BEGIN {
main(10, 20) # 使用自定义函数
}
其他知识点
匹配中文
awk '$1 ~/[一-龥]/' 匹配中文
awk '$1 ~/[\u4e00-\u9fa5]/' 这样反而不行,要不unicode转成对应的中文
awk对换行敏感
awk 'BEGIN{while("cat company.txt"|getline){dict[$1]=1}} {if($3 in dict)print NR}' target.txt
company.txt的文件是\r\n
结尾时,无法匹配
shell使用awk批量创建变量
eval $(awk 'BEGIN{print "v1='str1';v2='str2'"}') # 相当于执行了 v1='str1';v2='str2'
echo "v1=$var1"
echo "v2=$var2"