{{ cat }}'s docs

12.4.0: 正则基础-awk


1. awk

作用:超出我现在的想象,可以包含编程程序在awk里,运维里常用的还是处理字符串
语法结构:
awk [参数] '判断条件1{程序1}判断条件2{程序2;程序3}...'

条件操作符:

  • == 等于
  • > 大于
  • < 小于
  • != 不等于
  • >= 大于等于
  • <= 小于等于
  • ~ 包含
  • !~ 不包含

参数:

  • -v awk内部声明变量,-v var="linux.xiao5tech.com"
  • -F 指定$0值的分隔符

变量:

  • NR-'number of record' 表示处理的记录行数
  • FNR-'file number of record' 如果处理多个文件时,每个文件都是从1开始记录行数,而不是像NR一样,第二个文件是承接第一个文件继续累加的
  • NF-'number of field' 表示每一行记录被分割成了几个字段

用法举例:

## 外部变量可被引用,另外"-v"参数可以让awk内部声明变量

# 引用外部变量
a=500;awk -F: '$3=='$a'' /etc/passwd
essence:x:500:500::/home/essence:/bin/bash

# 使用-v参数声明内部变量
awk -v a=500 -F: '$3==a' /etc/passwd
essence:x:500:500::/home/essence:/bin/bash

# 在action中声明内部变量
awk -F: '$3==500 {a="  \"here is var\"";print $0 a}' /etc/passwd
test:x:500:500::/home/test:/bin/bash  "here is var"

# 引入后面的变量
cat /etc/passwd|awk -F: '$3==500 {print $0 a}' a="  \"here is var\""
test:x:500:500::/home/test:/bin/bash  "here is var"
# 仅限于用管道符时才可以这样用
## "~"符号来匹配字符串
# 用";"和"&&、||"来间隔命令,及其区别

## ";"分隔命令
# awk -F : '$1~/test/ ; $3~/500/' /etc/passwd
test:x:500:500::/home/test:/bin/bash
test:x:500:500::/home/test:/bin/bash

## "&&"分隔命令
# awk -F : '$1~/test/ && $3~/500/' /etc/passwd
test:x:500:500::/home/test:/bin/bash

## "||"分隔命令
# awk -F : '$1~/test/ || $3~/500/' /etc/passwd
test:x:500:500::/home/test:/bin/bash

## 为什么";"输出两遍,而"&&""||"输出一遍?
## ";"分隔时,前后两条命令无逻辑关系,单独输出
## "&&""||",前后两条命令有逻辑关系,综合输出
## 累加数字

# 累计$3为sum值,依次输出
awk -F : '{sum=sum+$3}{print sum}' /etc/passwd
0
1
3
6
......
926
1426

# 累计$3为sum值,汇总最后输出
awk -F : '{sum=sum+$3}END{print sum}' /etc/passwd
1426

## awk处理文件是按照行数来执行{程序1}{程序2}
## awk程序执行前会有判断条件,若判断条件为空,则每次都执行该程序
## 而END是告诉awk程序,直到处理完所有行数的字符,才执行该程序
## awk工具"for"的用法实例

## for循环和数组来统计passwd文件中用户shell类型及其出现次数汇总
# awk -F : '{for(i=NF;i<NF+1;i++) login[$i]++} \
END { for(k in login) printf("shell: %-15s time: %-2d\n" ,k,login[k])}' \
/etc/passwd
shell: /sbin/shutdown  time: 1
shell: /bin/bash       time: 2
shell: /sbin/nologin   time: 15
shell: /sbin/halt      time: 1
shell: /bin/sync       time: 1
============================================================
## 拆解分析
awk -F :       
## -F参数指定分隔符

'    
## 此单引号和下面的配对

{for(i=NF;i<NF+1;i++) login[$i]++}
##{for(表达式1;表达式2;表达式3) 执行语句}

## 表达式1,给i赋值为NF,这里值是7

## 表达式2,i变量肯定小于NF+1,条件为真,后面执行语句必然执行

## 执行命令login[$i]++,即login[$7]=默认值0+1,login[$7]是一个数组
## $7这里对应的值是shell类型,例如第一行root的$7为"/bin/bash"

## 表达式3,i++,i此时为NF+1,awk会跳到下一行继续执行上面的程序

## 退出for循环只是意味着awk读取的这一行信息被处理完,
## 下一行信息被读取的时候,for依然会启动

END { for(k in login) printf("shell: %-15s time: %-2d\n" ,k,login[k])}
## END是awk的保留字,意为前面所有程序执行完毕,退出awk之前执行的命令

## {for(* in 数组) 执行语句}

## for (k in login)意为,k是login[$7]数组中的一项

## printf("输出格式",输出项1,输出项2)
## 输出格式中%-15s意为%s字符串、15个字符长、-左对齐;
## %-2d意为左对齐2字符长的数字格式

'
#和前面的单引号配对,表示awk的程序范围

/etc/passwd
#作用目标文件
============================================================
## -F指定分隔符
# awk -F: '{print NF}' /etc/passwd  
## -F与分隔符之间可无空格,但分隔符与程序主体''部分必须用空格隔开

## 输出格式中的"-"代表左对齐,无"-"则右对齐
# awk -F: '{for(i=NF;i<NF+1;i++)login[$i]++}\
END{for(sh in login)\
printf("shell: %-15s time: %-2d\n",sh,login[sh])}' \
/etc/passwd
shell: /sbin/shutdown  time: 1
shell: /bin/bash       time: 2
shell: /sbin/nologin   time: 20
shell: /sbin/halt      time: 1
shell: /bin/sync       time: 1
# awk -F: '{for(i=NF;i<NF+1;i++)login[$i]++}\
END{for(sh in login)\
printf("shell: %15s time: %2d\n",sh,login[sh])}' \
/etc/passwd
shell:  /sbin/shutdown time:  1
shell:       /bin/bash time:  2
shell:   /sbin/nologin time: 20
shell:      /sbin/halt time:  1
shell:       /bin/sync time:  1
## awk工具"if"的用法实例

## 把/etc/passwd中的$1中的root替换成toor
# awk -F: '{if($1=="root")\
{OFS=":" ; $1="toor";print $0}}' /etc/passwd
toor:x:0:0:root:/root:/bin/bash
=======================================================
## 拆解分析
awk -F:
#-F参数指定分隔符

'
#此单引号和下面的配对,为awk的主体架构,缺少会报语法错误

{if($1=="root"){OFS=":" ; $1="toor";print $0}}
#{if(判断条件){子程序1;子程序2......}}
#$1=="root",当passwd中$1等于root时
#OFS可以指定输出分隔符
#if的执行语句{}中,执行语句间用";"间隔

'
#和前面的单引号配对,表示awk的程序范围

/etc/passwd
#作用目标文件
=======================================================
awk比较大小

## $3和数字比较大小有""和没有的区别

## 数字无引号比较
# echo -e "50a\n600\n5001"|awk '$1>5000'
50a
5001
## 50a比5000大?
## 因为5000首先以数字身份参与比较,但是遇到50a是字符串
## 所以5000自动转换成字符串去比较
## 字符串的比较方式是5与5比对,ascii码相同,然后0和0比对,ascii码相同
## a和0比对,a和0的ascii分别为61和48,所以a比0大

## 数字有引号比较
# echo -e "50a\n600\n40001"|awk '$1>"5000"'
50a
600
## 这里就是纯字符比较了


## 用"+"运算符来解决上面讨厌的字符问题

## 把$3前面加上"+"
# awk -F: '+$3>500' /etc/passwd
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
zpw:x:501:501::/home/zpw:/bin/bash
oracle:x:502:502::/oracle:/bin/bash
webapp:x:503:503::/webapp:/sbin/nologin
## 为什么呢?
## 加上"+"之后,字符被当作0处理了
# echo a | awk '{print $1,+$1,1+$1}'
a 0 1            <-原来。
## 字符被当作0后,字符后面的数字也被省略了
# echo "a1" | awk '{print $1,+$1,1+$1}'
a1 0 1          <-
## 但如果数字是在前面则保留前面的数字
# echo "1a" | awk '{print $1,+$1,1+$1}'
1a 1 2

awk练习题
用awk 打印整个test.txt (以下操作都是用awk工具实现,针对test.txt)
查找所有包含 'bash' 的行
用 ':' 作为分隔符,查找第三段等于0的行
用 ':' 作为分隔符,查找第一段为 'root' 的行,并把该段的 'root' 换成 'toor' (可以连同sed一起使用)
用 ':' 作为分隔符,打印最后一段
打印行数大于20的所有行
用 ':' 作为分隔符,打印所有第三段小于第四段的行
用 ':' 作为分隔符,打印第一段以及最后一段,并且中间用 '@' 连接 (例如,第一行应该是这样的形式 'root@/bin/bash' )
用 ':' 作为分隔符,把整个文档的第四段相加,求和

扩展:
cat t.txt | awk -F "," '{result[$3]++} END {for(i in result) print result[i],i}'
统计第3字段的重复次数

Contents