awk 入门教程

AWK 是三个人名的缩写

  • Alfred V. Aho
  • Peter J. Weinberger
  • Brian W. Kernighan

通常我们使用的是gawk,是 awk 的一种特殊实现,表示GNU awk

在 CentOS 上 awk 就是 gawk 的软链

$ ll `which awk`
/bin/awk -> gawk

在 Debian 上需要单独安装和使用

aptitude install -y gawk

基本用法

语法: awk [option] 'program' fileName

In outline, an awk program generally looks like this:

[pattern] { action }
    pattern [{ action }]
    …
    functionName(args) { … }
    …
-F,--field-separator: the value of the FS predefined variable. 指定输入分隔符。可以不指定,则使用默认FS。可以使用括号表达式 '[]' 指定多个分隔符。
-v var=val 定义变量

文件中的行称为记录(Records)
文件中的字段称为(Fields)

An action consists of one or more statements, enclosed in braces ('{…}').
The statements are separated by newlines or semicolons.
If you omit the action entirely, omit the braces as well. An omitted action is equivalent to '{ print $0 }'.
Variables are automatically initialized to zero.

awk的工作流程

  • 执行BEGIN{}中的语句
  • 逐行扫描和处理文件
  • 扫描处理完文件之后,执行END{}中的语句

print命令

语法: print item1,item2,...

  • 以逗号作为分隔符,输出格式以空白分隔
  • 输出的 item 可以是字符串、数值、字段变量,或是awk表达式
  • 如果省略 item,相当于打印整行 print $0(包含分隔符)

The simple statement 'print' with no items is equivalent to 'print $0'.
To print a blank line, use 'print ""'.

$ tail -3 /etc/fstab | awk '{print $2,$4}'
$ tail -3 /etc/fstab | awk '{print $2$4}'  # 不加逗号时,输出字段会连在一起
$ tail -3 /etc/fstab | awk '{print $2 $4}'
    swapdefaults
    /media/cdromdefaults
    /homedefaults,usrquota,grpquota
$ tail -3 /etc/fstab | awk '{print "hello",$2,$4,6}'  # 数字被当作字符输出,运算时依然是数值
    hello swap defaults 6
    hello /media/cdrom defaults 6
    hello /home defaults,usrquota,grpquota 6
$ tail -3 /etc/fstab | awk '{print "hello: $1"}'  # $1放在引号里面不会被解析,被当作字符串输出
    hello: $1
    hello: $1
    hello: $1
$ tail -3 /etc/fstab | awk '{print "hello: "$1}'
    hello: UUID=4b27a61a-4111-4d30-96ac-93cff82b227e
    hello: UUID=3a3edcd8-a24f-414a-ace6-9952a3ca4891

printf命令

语法: printf FORMAT(格式符),item1,item2,...

  • FORMAT必须给出
  • printf不会自动换行,需要手动指定换行符\n
  • FORMAT中需要分别为后面的每个item指定一个格式化符号
  • printf不是管道命令

格式符(占位符)

  • %c: 显示字符的ASCII码
  • %d,%i: 显示十进制整数
  • %e,%E: 科学计数法显示数值
  • %f: 浮点数
  • %g,%G: 以科学计数法或浮点形式显示数值
  • %s: 显示字符串
  • %u: 无符号整数
  • %%: 显示%自身
awk -F: '{printf "%s\n",$1}' /etc/passwd  # 格式符需要用引号引起来
awk -F: '{printf "username: %s\n",$1}' /etc/passwd
awk -F: '{printf "username: %s\nuid: %d\n",$1,$3}' /etc/passwd  # 这里打印多个字段时,$1对应第一串格式,$3对应第二串格式

修饰符

#[.#]: 第一个数字控制显示的宽度;第二个数字表示小数点后的精度 %3.1f

-: 表示左对齐,默认是右对齐
$ awk -F: '{printf "username: %-20s uid: %d\n",$1,$3}' /etc/passwd  # 指定20个字符的宽度显示$1,并左对齐
    username: root                  uid: 0
    username: bin                   uid: 1
    username: daemon                uid: 2
$ awk -F: '{printf "username: %20s uid: %d\n",$1,$3}' /etc/passwd
    username:                 root  uid: 0
    username:                  bin  uid: 1
    username:               daemon  uid: 2

+: 显示数值的正负符号 %+d

$ awk -F: '{printf "%-20s | %+10d\n",$1,$3}' /etc/passwd

变量

内置变量

FS: The input field separator, a space by default. 输入时的字段分隔符,与"-F"作用相同
  $ awk -v FS=':' '{print $1}' /etc/passwd |head -3 
    root
    bin
    daemon
  $ awk -v FS=: '{print $1}' /etc/passwd  # FS后面的引号可省略
  $ awk -F: '{print $1}' /etc/passwd

OFS: The output field separator, a space by default. 输出时的字段分隔符
  $ awk -v FS=: -v OFS=: '{print $1,$3,$7}' /etc/passwd | head -3
    root: 0: /bin/bash
    bin: 1: /sbin/nologin
    daemon: 2: /sbin/nologin

RS: The input record separator, by default a newline. 输入时的分隔符,默认为换行符
  $ awk -v RS=' ' '{print}' /etc/passwd  # 指定以空格为输入换行符,即有空白的地方会换行,打印输出时原有的换行符依然会换行
  
  $ vim file2
    a: b c: d
    x: y: z
  $ awk -v RS=':' '{print $1}' file2
    a
    b
    d
    y
    z
  $ awk -v RS=':' '{print $2}' file2  # 这里处理d x时,把原有的换行符当作空白处理了
    c
    x

ORS: The output record separator, by default a newline. 输出时的分隔符,默认为换行符
  $ awk -v RS=' ' -v ORS='#' '{print}' /etc/passwd | tail -3  # 指定以#为输出分隔符,实际结果是空白换行符被替换为#输出,原有的换行符依然会换行
    radvd: x: 75: 75: radvd#user: /: /sbin/nologin
    sssd: x: 983: 978: User#for#sssd: /: /sbin/nologin
    gdm: x: 42: 42: : /var/lib/gdm: /sbin/nologin

NF: The number of fields in the current input record. 每一行的字段数量
  $ awk '{print NF}' /etc/fstab  # 显示字段数量
  $ awk '{print $NF}' /etc/fstab  # $NF显示最后一个字段

NR: The total number of input records seen so far. 当前读取的总行数
  $ awk '{print NR}' /etc/fstab  # 显示每一行的行号
  $ awk '{print NR}' /etc/fstab /etc/issue  # 跟多个文件时,会连在一起连续编号

FNR: The input record number in the current input file. 当前数据文件中的数据行数;对每个文件单独显示行号
  $ awk '{print FNR}' /etc/fstab /etc/issue  # 两个文件单独编号

FILENAME: The name of the current input file. 当前读取的文件的文件名
  $ awk '{print FILENAME}' /etc/fstab /etc/issue  # 每读取一行,打印一次当前读取的文件的文件名

ARGC: The number of command line arguments.does not include options to gawk, or the program source. 命令行参数的数量
  $ awk 'BEGIN{print ARGC}' /etc/fstab /etc/issue

ARGV: Array of command line arguments. The array is indexed from 0 to ARGC-1. 命令行参数的数组
  $ awk 'BEGIN{print ARGV[0]}' /etc/fstab /etc/issue
  $ awk 'BEGIN{print ARGV[1]}' /etc/fstab /etc/issue
  $ awk 'BEGIN{print ARGV[2]}' /etc/fstab /etc/issue

自定义变量

1、-v var=value
$ awk -v test='hello awk' '{print test}' /etc/fstab
$ awk -v test='hello awk' 'BEGIN{print test}' /etc/fstab
  
2、在program中直接定义
$ awk 'BEGIN{test="hello gawk"; print test}'  # BEGIN模式,不对文件进行处理

操作符

算术运算

x+y, x-y, x*y, x/y, x^y, x%y

赋值操作

=, +=, -=, *=, /=, %=, ^=
++, --

比较操作

>, <, >=, <=, ==, !=, in

模式匹配

~: 匹配
!~: 不匹配
awk '$0 ~ /root/' /etc/passwd | wc -l 
awk '$0 !~ /root/' /etc/passwd | wc -l

逻辑操作

pattern && pattern
pattern || pattern
! pattern
awk -F: '{if ($3>=0 && $3<=1000) {print $1,$3}}' /etc/passwd
awk -F: '{if ($3==0 || $3<=1000) {print $1,$3}}' /etc/passwd
awk -F: '{if (!($3>=500)) {print $1,$3}}' /etc/passwd

条件表达式

The C conditional expression. This has the form expr1 ? expr2 : expr3.  
If expr1 is true, the value of the expression is expr2, otherwise it is expr3. 

pattern ? pattern : pattern
selector ? if-true-expression : if-false-expression  # 如果条件表达式为真,执行true语句,为假则执行false语句
awk -F: '{$3 >= 1000 ? usertype="common user" : usertype="sysadmin or sysuser"; printf "%15s: %-s\n",$1,usertype}' /etc/passwd

Patterns

(1)empty: 空模式,处理每一行

(2)/pattern/: 仅处理模式匹配到的行;注意模式要写在两条斜线中间/regular expression/,模式支持正则表达式

$ awk '/^UUID/ {print $1}' /etc/fstab  # 打印以UUID开头的行
$ awk '!/^UUID/ {print $1}' /etc/fstab  # 取反,打印不以UUID开头的行

(3)relational expression: 关系表达式;结果有"真""假",为真才被处理,为假则过滤掉不处理;
    真: 结果为非0值,非空字符串;

$ awk -F: '$3 >= 1000 {print $1,$3}' /etc/passwd  # 处理uid大于等于1000的行;
$ awk -F: '$NF == "/bin/bash" {print $1,$NF}' /etc/passwd  # 处理最后一个字段为/bin/bash的行
$ awk -F: '$NF ~ /\/bash$/ {print $1,$NF}' /etc/passwd  # 处理最后一个字段以/bash结尾的行

(4)line ranges: 行范围,即地址定界
    /PAT1/,/PAT2/: 第一次匹配到PAT1的行到第一次匹配到PAT2的行

$ awk -F: '/^root/,/^mysql/ {print $1}' /etc/passwd

(5)BEGIN/END模式
  BEGIN{}: 仅在开始处理文本之前执行一次
  END{}: 仅在文本处理完成之后执行一次

$ awk -F: '{sum+=$3} END{print sum}' /etc/passwd  # 求当前系统上所有用户uid之和
$ awk -F: '{sum+=$3} END{print sum/NR}' /etc/passwd  # 求平均数

Actions

  • Expressions
  • Control Statements
  • Compound Statements
  • Input Statements
  • Output Statements

控制语句(Control Statements)

  • if (condition) statement [ else statement ]
  • while (condition) statement
  • do statement while (condition) # 先执行一次循环体,再判断条件
  • for (expr1; expr2; expr3) statement
  • break
  • continue
  • next
  • delete array[index] # 删除数组中的某个元素
  • delete array # 删除整个数组
  • exit
  • switch (expression) {
    case value|regex: statement
    ...
    [ default: statement ]
    }

if-else

awk -F: '{if ($3 >= 1000) print $1,$3}' /etc/passwd
awk -F: '{if ($3 >= 1000) {printf "common user: %s\n",$1} else {printf "root or sysuser: %s\n",$1}}' /etc/passwd
awk -F: '{if ($NF == "/bin/bash") print $1}' /etc/passwd  # 处理最后一个字段为/bin/bash的行
awk '{if (NF > 5) print $0}' /etc/passwd  # 显示字段数大于5的行
df -h | awk -F% '/^\/dev/ {print $1}' | awk '{if ($NF >= 10) print $1,$NF}'

while

# while (condition) statement

awk '/^[[:space:]]*linux16/ {i = 1; while (i <= NF) {print $i,length($i); i++}}' /etc/grub2.cfg  # 过滤出以linux16开头的行,并统计每一个字段的长度
awk '/^[[:space:]]*linux16/ {i = 1; while (i <= NF) {if (length($i) >= 7) {print $i,length($i)}; i++}}' /etc/grub2.cfg  # 进一步过滤出长度大于等于7的字段

do-while

# do statement while (condition) 至少执行一次循环体

awk 'BEGIN{sum=0; i=0; do {sum+=i; i++} while (i <= 100); print sum}'  # 求1-100之和

for

# for (variable assignment; condition; iteration process) statement

awk '/^[[:space:]]*linux16/ {for (i=1; i<=NF; i++) print $i,length($i)}' /etc/grub2.cfg  # 打印以linux16的行,并统计其长度

next

# 提前结束对本行的处理而直接进入下一行,类似continue。

awk -F: '{if ($3 % 2 != 0) next; print $1,$3}' /etc/passwd  # 处理uid为偶数的行
awk -F: '{if ($3 % 2 == 0) print $1,$3}' /etc/passwd

array

关联数组: array[index-expression]

index-expression

  • 可使用任意字符串,字符串要使用双引号引起来。
  • 在引用时,如果某数组元素事先不存在,awk会自动创建此元素,并将其值初始化为空,作为数字运算时其值为0。

遍历数组中的每个元素的index,要使用 for 语句进行 in 判断。

for (var in array) statement

var 会遍历 array 的每个索引,代表的是 index,而不是元素的值。

$ awk 'BEGIN{weekdays["mon"]="Monday"; weekdays["tue"]="Tuesday"; for (i in weekdays) {print i,weekdays[i]}}'

$ netstat -tan | gawk '/^tcp\>/ {print}'
    tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN
    tcp        0      0 127.0.0.1:53            0.0.0.0:*               LISTEN 
    tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
  
$ netstat -tan | gawk '/^tcp\>/ {state[$NF]++} END{for (i in state) {print i,state[i]}}'  # state[$NF]事先不存在,引用时自动创建该元素,其值为空,数值大小为0
    LISTEN 8
    ESTABLISHED 1

$ awk '{ip[$1]++}; END{for (i in ip) print i,ip[i]}' /var/log/nginx/access.log
$ awk '/^UUID/ {fs[$3]++}; END{for (i in fs) {print i,fs[i]}}'  # 统计/etc/fstab文件中每个文件系统类型出现的次数
$ awk '{for (i=1; i<=NF; i++) {count[$i]++}}; END{for (i in count) {print i,count[i]}}' /etc/fstab  # 统计指定文件中每个单词出现的次数

内置函数

数值函数(Numeric Functions)

rand(): 返回0-1之间的一个随机数 0 ≤ N < 1.
int(expr): 截取整数; Truncate to integer.

字符函数(String Functions)

length([string]): 返回字符串长度,如果未指定参数,则返回$0的长度。
sub(regexp, replacement [, target]): 在 target 中搜索 regexp 模式,并将其第一次匹配到的字符替换 replacement。
gsub(regexp, replacement [, target]): 类似 sub,但是全局替换。
split(s,a[,r]): 以r为分隔符切割字符串s,并将切割后的结果保存至a所表示的数组中。
substr(string, start [, length ]): 从字符串 start 位置开始截取 length 个字符,如果未指定 length,则截取到末尾。
index(in, find): 在字符串中查找指定字符,并返回第一次查询到的索引。如果未找到,则返回0。
awk '{print length()}' /etc/passwd  # 如果未指定参数,则默认返回$0即整行的长度
netstat -tan | gawk '/^tcp\>/ {split($5,ip,":"); print ip[1]}'
netstat -tan | gawk '/^tcp\>/ {split($5,ip,":"); count[ip[1]]++}; END{for (i in count) {print i,count[i]}}'
awk '{print substr($1,3)}' /etc/passwd  # 截取第一个字段,从第三个字符开始到最后
awk -F: '{print substr($1,3)}' /etc/passwd
# F115!16201!1174113017250745 10.86.96.41 211.140.16.1 200703180718
# F125!16202!1174113327151715 10.86.96.42 211.140.16.2 200703180728
# F235!16203!1174113737250745 10.86.96.43 211.140.16.3 200703180738
# F245!16204!1174113847250745 10.86.96.44 211.140.16.4 200703180748
# F355!16205!1174115827252725 10.86.96.45 211.140.16.5 200703180758

$ awk -F '[ !]' '{print substr($3,6)}' info.txt  # 可以用中括号同时指定两个分隔符
13017250745
13327151715
13737250745
13847250745
15827252725

时间函数(Time Functions)

strftime([format [,timestamp]]): 按照指定的格式(format)格式化时间戳(timestamp); Format timestamp according to the specification in format.
systime(): 按秒返回当前时间,即返回时间戳。
awk 'BEGIN{print systime()}'  # 等同于`date +%s`
ping 114.114.114.114 | awk '{now=strftime("%Y-%m-%d %H:%M:%S",systime()); printf "%s : %s\n",now,$0; fflush();}'

Input/Output Functions

system(command): Execute the command, and return the exit status.
fflush([file]): 用于清空缓冲流,手动将 buffer 落盘。
awk 'BEGIN{system("hostname")}'  # 系统命令需要用双引号引起来

打印指定列到最后一列

history | awk '{for (i=2; i<=NF; i++) printf "%s ",$i; print ""}'
history | awk '{print substr($0, index($0,$3))}'

性能比较: awk 比普通 shell 快得多

$ time (awk 'BEGIN{sum = 0; for (i=0; i<=1000000; i++) sum += i; print sum}')
    real    0m0.073s
    user    0m0.067s
    sys	    0m0.004s

$ time (sum=0; for i in $(seq 1000000); do let sum+=$i; done; echo $sum)
    real    0m3.403s
    user    0m3.231s
    sys     0m0.086s

参考文档

posted @ 2017-05-07 17:23  KeithTt  阅读(316)  评论(0编辑  收藏  举报