Linux之awk命令实战

AWK简介

awk是一种编程语言,用于在Linux/Unix下对文本和数据进行处理。数据可以来自标准输入、一个或多个文件,或其它命令的输出。支持用户自定义函数和动态正则表达式等先进功能,它可以在命令行中使用,但更多是作为脚本来使用。
awk的处理文本和数据时会逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行指定操作。如果没有指定操作,则把匹配的行显示到标准输出(屏幕);如果没有指定模式,则所有行都要被指定操作处理。

AWK命令

1
$ awk '{Pattern Action}' {FileNames}

Pattern表示awk在数据中查找的内容,而Action是在找到匹配内容时所执行的一系列命令。花括号({})不一定必须在程序中出现,但它们用于根据特定的模式对一系列指令进行分组。当Pattern是正则表达式时,需要用斜杠括起来。
awk的最基本功能是在文件或者字符串中基于指定规则浏览和抽取信息,awk抽取信息后才能进行其他文本操作。完整的awk脚本通常用来格式化文本文件中的信息,通常awk是以文件的一行为处理单位。awk每接收文件的一行,然后执行相应的命令,来处理文本。

AWK常见概念

  • 记录
    awk把每一个以换行符结束的行称为一个记录。
  • 记录分隔符
    默认的输入和输出的分隔符都是回车,保存在环境变量ORS和RS中。

  • 记录中每个单词称做“域”,默认情况下以空格或制表符Tab分隔。awk可跟踪域的个数,并在环境变量NF中保存该值。

    1
    $ awk '{print $1,$5}' TestFile #输出TestFile文件中第一和第五个以空格或制表符Tab分隔的列(域)。
  • 域分隔符
    环境变量FS保存输入域分隔符的值,默认是空格或制表符Tab。输出域的分隔符默认是一个空格,保存在OFS中。
    我们可以通过-F命令行选项修改FS的值,可以同时使用多个域分隔符,但需要把分隔符写成放到方括号中,

    1
    2
    3
    $ awk -F: '{print $1","$5}' TestFile #输出TestFile文件中第一和第五个以冒号分隔的列(域)。
    #上述命令$1$5间的逗号(",")就是设定的输出域分隔符OFS的值。
    $ awk -F'[:/t]' '{print $1,$5}' TestFile #输出TestFile文件中第一和第五个以冒号、空格或制表符Tab分隔的列(域)。
  • $0变量
    它指的是整条记录。

    1
    $ awk '{print $0}' TestFile #将输出TestFile文件中的所有记录。
  • 变量NR
    计数器,每处理完一条记录,NR的值就增加1。

    1
    $ awk '{print NR,$0}' TestFile #将输出TestFile文件中所有记录,并在每条记录前显示记录号。
  • 更多环境变量请见>>AWK环境变量

AWK形式

使用awk命令有两种形式,一种是直接在命令行中输入awk命令,另一种则是在命令行中引入写好的awk脚本文件。

  • 命令形式

    1
    2
    3
    4
    $ awk [-F FieldSeparator] 'AwkCommands' InputFile(s)
    # [-F FieldSeparator] 是可选项,用来指定域分隔符,默认使用空格或制表符Tab
    # AwkCommands 表示所需要的awk命令
    # InputFile(s) 表示待处理的一个或多个文件
  • 文件形式

    1
    2
    3
    4
    $ awk -f AwkScriptFile InputFile(s)
    # -f 表示加载awk命令文件
    # AwkScriptFile 表示编写好的awk脚本文件
    # InputFile(s) 表示待处理的一个或多个文件

AWK模式和操作

Pattern和Action二者是可选的,如果没有模式,则Action应用到全部记录,如果没有Action,则输出匹配全部记录。
默认情况下,每一个输入行都是一条记录,但用户可通过RS变量指定不同的分隔符进行分隔。

AWK模式

  • 正则表达式:使用通配符的扩展集。
  • 关系表达式:可以用下面运算符表中的关系运算符进行操作,可以是字符串或数字的比较,如$2>$1选择第二个字段比第一个字段长的行。
  • 模式匹配表达式:用运算符~(匹配)和~!(不匹配)。
  • 模式:指定一个行的范围。该语法不能包括BEGIN和END模式。
  • BEGIN:让用户指定在第一条输入记录被处理之前所发生的动作,通常可在这里设置全局变量。
  • END:让用户在最后一条输入记录被读取之后发生的动作。

AWK操作

操作由一人或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内。主要有四部份:

  • 变量或数组赋值
  • 输出命令
  • 内置函数
  • 控制流命令

AWK选项

  • -F FieldSeparator
    指定域分隔符为FieldSeparator,FieldSeparator可以是一个字符串或正则表达式。等价于--field-separator FieldSeparator
  • -v var=Value
    给用户自定义变量赋值为Value。等价于--asign var=Value
  • -f AwkScripFile
    使用从指定awk脚本文件AwkScripFile中加载命令。等价于--afile AwkScripFile
  • -mf N and -mr N
    对N值设置内在限制,-mf选项限制分配给N的最大块数目;-mr选项限制记录的最大数目。
    注意:这两个功能是Bell实验室版awk的扩展功能,在标准awk中不适用。
  • -W compact
    在兼容模式下运行awk,所有的awk扩展都被忽略,所以此时gawk行为和标准的awk完全一样。等价于--compat-W traditional--traditional
  • -W copyleft
    打印简短的版权信息。等价于--copyleft-W copyright--copyright
  • -W help
    打印全部awk选项和每个选项的简短说明。等价于--help-W usage--usage
  • -W lint
    打印不能向传统unix平台移植的结构的警告。等价于--lint
  • -W lint-old
    打印关于不能向传统unix平台移植的结构的警告。等价于--lint-old
  • -W posix
    打开兼容模式,但有以下限制:不识别/x、函数关键字、func、换码序列以及当FieldSeparator是一个空格时将新行作为一个域分隔符;
    操作符=不能代替^和^=;fflush无效
  • -W re-interval
    允许间隔正则表达式的使用,参考(grep中的Posix字符类),如括号表达式[[:alpha:]]。等价于--re-interval
  • -W source ProgramText
    使用program-text作为源代码,可与-f命令混用。等价于--source ProgramText
  • -W version
    打印bug报告信息的版本。等价于--version

AWK环境变量

变量 描述
$n 当前记录的第n个字段,字段间由FS分隔。
$0 完整的输入记录。
ARGC 命令行参数的数目。
ARGIND 命令行中当前文件的位置(从0开始算)。
ARGV 包含命令行参数的数组。
CONVFMT 数字转换格式(默认值为%.6g)
ENVIRON 环境变量关联数组。
ERRNO 最后一个系统错误的描述。
FIELDWIDTHS 字段宽度列表(用空格键分隔)。
FILENAME 当前文件名。
FNR 同NR,但相对于当前文件。
FS 字段分隔符(默认是任何空格)。
IGNORECASE 如果为真,则进行忽略大小写的匹配。
NF 当前记录中的字段数。
NR 当前记录数。
OFMT 数字的输出格式(默认值是%.6g)。
OFS 输出字段分隔符(默认值是一个空格)。
ORS 输出记录分隔符(默认值是一个换行符)。
RLENGTH 由match函数所匹配的字符串的长度。
RS 记录分隔符(默认是一个换行符)。
RSTART 由match函数所匹配的字符串的第一个位置。
SUBSEP 数组下标分隔符(默认值是/034)。

AWK运算符

运算符 描述
=   +=   -=   =   /=   %=   ^=   \*= 赋值
?: 三元条件表达式
|| 逻辑或
&& 逻辑与
~   ~! 匹配正则表达式和不匹配正则表达式
<   <=   >   >=   !=   == 关系运算符
空格 连接
+   - 加,减
*   /   & 乘,除与求余
+   -   ! 一元加,减和逻辑非
^   *** 求幂
++   -- 增加或减少,作为前缀或后缀
$ 字段引用
in 数组成员
1
2
3
4
5
$ awk '$1 ~/^bocai/' TestFile #显示TestFile文件第一列以root开头的所有行
$ awk '/bo/,/cai/' TestFile #显示TestFile第一次出现bo到第一次出现cai之间的所有行
$ awk '{max = ($1 > $3) ? $1: $3} {print max}' TestFile #输出第一域和第三个域中较大的数
$ awk '$1 + $2 < 100' TestFile #显示TestFile文件第一和第二域相加大于100的所有行
$ awk '$1 > 5 && $2 < 10' TestFile #显示TestFile文件第一域大于5且第二域小于10的所有行

AWK实用用例

  • 显示/etc/passwd中已r开头的账号

    1
    2
    3
    $ cat /etc/passwd | awk -F: '/^r/ {print $1}'
    root
    redis
  • 显示/etc/passwd第一个以root开头账号,到第一个以mysql开头账号间的所有账号

    1
    2
    3
    4
    5
    $ cat /etc/passwd | awk -F: '/^root/,/^mysql/ {print $1}'
    root
    bin
    ...
    mysql
  • 显示/etc/passwd中的账号和各账号对应的用户目录,并标记记录号。

    1
    2
    3
    4
    5
    6
    $ cat /etc/passwd | awk -F ':' '{print NR "\t" $1 "\t" $6}'
    1 root /root
    2 bin /bin
    3 adm /var/adm
    ...
    16 bocai /home/bocai
  • 显示/etc/passwd中的账号和各账号对应的用户目录,并标记记录号,在第一行输出“NR Name Home”,最后一行输出“Total: XX accounts”。

    1
    2
    3
    4
    5
    6
    7
    8
    $ cat /etc/passwd | awk -F ':' 'BEGIN{count = 0; print "NR\tName\tHome"} { count++ ;print NR "\t" $1 "\t" $6} END {print "Total:\t" count " accounts" }'
    NR Name Home
    1 root /root
    2 bin /bin
    3 adm /var/adm
    ...
    16 bocai /home/bocai
    Total: 16 accounts
  • 统计目录/usr/bin/下大小小于5的文件情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ ls -l /usr/bin/ | awk 'BEGIN{size=0;count=0; print"[Begin]\tSize = "size } {if($5<5){size+=$5;count++;print $5"\t"$9}} END {print"[End]\tSize = "size;print "\tCount = "count;print "\tAVE = "size/count } '
    [Begin] Size = 0
    2 atq
    2 atrm
    ...
    2 xzcat
    [End] Size = 80
    Count = 27
    AVE = 2.96296
  • 统计目录/usr/bin/下大小在10与20之间的文件大小分布

    1
    2
    3
    4
    5
    6
    7
    8
    $ ls -l /usr/bin/ | awk 'BEGIN {print "Size\tNum"}{if($5>10 && $5 <20) m[$5]++} END {for(i in m) print i "\t" m[i]}'
    Size Num
    17 1
    18 3
    13 13
    14 6
    15 2
    16 2