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 }'
    

运行流程

img

  1. awk以逐行的形式处理文件

  2. BEGIN之后的命令会先于公共语句块执行

    BEGIN语句块在awk开始从输入流中读取行之前被执行(除非调用了 getline)。这是一个可选的语句块,诸如变量初始化、打印输出表格的表头等语句通常都可以放在BEGIN语句块中。

  3. 对于匹配PATTERN的行,awk会对其执行PATTERN之后的命令

    这个语句块是可选的。如果不提供,则默认执行{ print },即打印所读取到的每一行。 如果提供,则每个pattern依次测试每个输入行。对于匹配到行的模式,其对应的命令(也许包含多步)得到执行,然后读取下一行并继续匹配,直到所有的输入读取完毕。

  4. 最后,在处理完整个文件之后,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+018.066000E+01e的大小写
%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 或者逗号分隔

参考:https://ipcmen.com/awk

格式化脚本

[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()有两个相关的特殊变量,分别是RSTARTRLENGTH。变量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命令的输出)赋值给$0getline后面的变量。由于该文件是刚打开,并没有被awk读入一行,那么getline返回的是该文件的第一行,而不是隔行,并赋值。

    getline 会记住重定向的文件读取到哪一行了,并不会重复执行getline var < file 重复读取文件,而是一次读取一行

  • getline执行后会更新NFNRFNR等这些内部变量。

例子:

比较 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"

评论