awk--基本操作一

2021-07-21  本文已影响0人  一萍之春

通过学习《awk精通》整理

作者: 骏马金龙
学习链接: https://www.junmajinlong.com/how_to_nav_posts/
学习来源: 骏马金龙
思维导图查看

what

awk是一个文本处理工具

awk基本用法

铺垫:文件读取的几种方式

1. 按字符数量读取:每一次可以读取一个字符,或者多个字符,直到把整个文件读取完
         while read -n 1 char;do echo $char;done <a.txt
2. 按照分隔符进行读取:一直读取直到遇到了分隔符才停止,下次继续从分隔的位置处向后读取,直到读完整个 文件
while read -d "m" chars;do echo "$chars";done <a.txt 
# 以字符'm'为分割点
3. 按行读取:每次读取一行,直到把整个文件读完
是按照分隔符读取的一种特殊情况:将分隔符指定为了换行符 \n while read line;do echo "$line";done <a.txt
4. 一次性读取整个文件 是按字符数量读取的特殊情况,也是按分隔符读取的特殊情况
read -N 10000000 data <a.txt
echo "$data #展示

read -d '_' data <a.txt
echo "$data" #展示

awk用法入门

 awk 'awk_program' a.txt
awk '{print $0}' a.txt
awk '{print $0}{print $0;print $0}' a.txt

BEGIN和END语句块

awk 'BEGIN{print "我在前面"}{print $0}' a.txt
awk 'END{print "我在后面"}{print $0}' a.txt
awk 'BEGIN{print "我在前面"}{print $0}END{print "我在后面"}' a.txt

BEGIN代码块:

END代码块:

main代码块:

安装新版本gawk

# 1.下载
wget --no-check-certificate https://mirrors.tuna.tsinghua.edu.cn/gnu/gawk/gawk-4.2.0.tar.gz
# 2.解压、进入解压后目录
tar xf gawk-4.2.0.tar.gz
cd gawk-4.2.0/
# 3.编译
./configure --prefix=/usr/local/gawk4.2 && make && make install
# 4.创建一个软链接:让awk指向刚新装的gawk版本
ln -fs /usr/local/gawk4.2/bin/gawk /usr/bin/awk
# 此时,调用awk将调用新版本的gawk,调用gawk将调用旧版本的gawk awk --version
gawk --version

系统深入awk

awk命令行结构和语法结构

在Shell命令行当中,双短横线 -- 表示选项到此结束,后面的都是命令的参数。

awk [ -- ] program-text file ...
awk -f program-file [ -- ] file ...
awk -e program-text [ -- ] file ...

cmd -x -r root -ppassword a.txt b.txt c.txt
# 1.选项分为长选项和短选项 
# 2.选项分为3种:
#       (1).不带参数的选项
#       (2).是带参数的选项,如果该选项后面没有给参数,则报错
#       (3).参数可选的选项,选项后面可以跟参数,也可以不跟参数
#          参数可选选项,如果要接参数,则必须将参数紧紧跟在选项后面,不能使用空格分隔选项和参数
#3.两种参数:
#   (1).选项型参数
#   (2).非选项型参数

awk的语法充斥着 pattern{action} 的模式,它们称为awk rule:

awk 'BEGIN{n=3} /^[0-9]/{$1>5}{$1=333;print $1} /Alice/{print "Alice"} END{print "hello"}' a.txt

pattern和action

对于 pattern{action} 语句结构(都称之为语句块),其中的pattern部分可以使用下面列出的模式:

# 特殊pattern BEGIN
END
# 布尔代码块
/regular expression/ # 正则匹配成功与否 /a.*ef/{action}
relational expression # 即等值比较、大小比较 3>2{action}
pattern && pattern   # 逻辑与 3>2 && 3>1 {action}

pattern || pattern # 逻辑或 3>2 || 3<1 {action}
! pattern # 逻辑取反 !/a.*ef/{action} 
(pattern) # 改变优先级
pattern ? pattern : pattern # 三目运算符决定的布尔值

# 范围pattern,非布尔代码块
pattern1, pattern2 # 范围,pat1打开、pat2关闭,即flip,flop模式

action部分,可以是任何语句,例如print语句。

awk读取文件

详细分析awk如何读取文件

awk读取输入文件时,每次读取一条记录(record)(默认情况下按行读取,所以此时记录就是行)。
每读取一条记录,将其保存到 $0中,然后执行一次main代码段。

awk '{print $0}' a.txt

如果是空文件,则因为无法读取到任何一条记录,将导致直接关闭文件,而不会进入main代码段。
但是BEGIN END会进行执行。

touch x.log # 创建一个空文件
awk '{print "hello world"}' x.log

可设置表示输入记录分隔符的预定义变量RS(Record Separator)来改变每次读取的记录模式。

# RS="\n" 、 RS="m"
awk 'BEGIN{RS="\n"}{print $0}' a.txt 
awk 'BEGIN{RS="m"}{print $0}' a.txt

RS通常设置在BEGIN代码块中,因为要先于读取文件就确定好RS分隔符。

RS指定输入记录分隔符时,所读取的记录中是不包含分隔符字符的。
例如 RS="a" ,则 $0 中一定不可能出现 字符a。

RS两种可能情况:

# 按段落读取:RS=''
$ awk 'BEGIN{RS=''}{print $0"------"}' a.txt
# 一次性读取所有数据:RS='\0' RS="^$"
$ awk 'BEGIN{RS='\0'}{print $0"------"}' a.txt $ awk 'BEGIN{RS='^$'}{print $0"------"}' a.txt
# 忽略空行:RS='\n+'
$ awk 'BEGIN{RS='\n+'}{print $0"------"}' a.txt
# 忽略大小写:预定义变量IGNORECASE设置为非0值
$ awk 'BEGIN{IGNORECASE=1}{print $0"------"}' RS='[ab]' a.txt
预定义变量RT:
在awk每次读完一条记录时,会设置一个称为RT的预定义变量,表示Record Termination。
当RS为单个字符时,RT的值和RS的值是相同的。
当RS为多个字符(正则表达式)时,则RT设置为正则匹配到记录分隔符之后,真正用于划分记录时的字符。 
当无法匹配到记录分隔符时,RT设置为控制空字符串(即默认的初始值)。
awk 'BEGIN{RS="(fe)?male"}{print RT}' a.txt

两种行号:NR和FNR

在读取每条记录之后,将其赋值给$0,同时还会设置NR、FNR、RT。

awk '{print NR}' a.txt a.txt
awk '{print FNR}' a.txt a.txt

详细的分段字段分割

awk读取每一条记录之后,会将其赋值给 0 ,同时还会对这条记录按照预定义变量FS划分字段,将划分好的各个\ 字段分别赋值给1 23 4...N ,同时将划分的字段数量赋值给预定义变量NF。

引用字段的方式

$N 引用字段:

awk '{n = 5;print $n}' a.txt
awk '{print $(2+2)}' a.txt # 括号必不可少,用于改变优先级 
awk '{print $(NF-3)}' a.txt

分割字段的方式

读取record之后,将使用预定义变量FS、FIELDWIDTHS或FPAT中的一种来分割字段。分割完成之后,再进入
main代码段(所以,在main中设置FS对本次已经读取的record是没有影响的,但会影响下次读取)。

FS或-F

FS 或者 -F :字段分隔符

FIELDWIDTHS

指定预定义变量FIELDWIDTHS按字符宽度分割字段,这是gawk提供的高级功能。在处理某字段缺失时非常好用。
用法:

# 没取完的字符串DDD被丢弃,且NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 2"}{print $1,$2,$3,$4}' <<<"AABBBCCDDDD" AA BBB CC
# 字符串不够长度时无视
$ awk 'BEGIN{FIELDWIDTHS="2 3 2 100"}{print $1,$2,$3,$4"-"}' <<<"AABBBCCDDDD" AA BBB CC DDDD-
# *号取剩余所有,NF=3
$ awk 'BEGIN{FIELDWIDTHS="2 3 *"}{print $1,$2,$3}' <<<"AABBBCCDDDD" AA BBB CCDDDD
# 字段数多了,则取完字符串即可,NF=2
$ awk 'BEGIN{FIELDWIDTHS="2 30 *"}{print $1,$2,NF}' <<<"AABBBCCDDDD" AA BBBCCDDDD 2

示例2:处理某些字段缺失的数据。
如果按照常规的FS进行字段分割,则对于缺失字段的行和没有缺失字段的行很难统一处理,但使用FIELDWIDTHS则非常方便。
假设a.txt文本内容如下:

ID  name    gender  age  email          phone
1   Bob     male    28   abc@qq.com     18023394012
2   Alice   female  24   def@gmail.com  18084925203
3   Tony    male    21   aaa@163.com    17048792503
4   Kevin   male    21                  17023929033
5   Alex    male    18   ccc@xyz.com    18185904230
6   Andy    female  22   ddd@139.com    18923902352
7   Jerry   female  25   exdsa@189.com  18785234906
8   Peter   male    20   bax@qq.com     17729348758
9   Steven  female  23   bc@sohu.com    15947893212
10  Bruce   female  27   bcbd@139.com   13942943905

因为email字段有的是空字段,所以直接用FS划分字段不便处理。可使用FIELDWIDTHS。

# 字段1:4字符
# 字段2:8字符
# 字段3:8字符
# 字段4:2字符
# 字段5:先跳过3字符,再读13字符,该字段13字符
# 字段6:先跳过2字符,再读11字符,该字段11字符
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
NR>1{
    print "<"$1">","<"$2">","<"$3">","<"$4">","<"$5">","<"$6">"
}' a.txt
# 如果email为空,则输出它
awk '
BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"} NR>1{
    if($5 ~ /^ +$/){print $0}
}' a.txt

FPAT

FS是指定字段分隔符,来取得除分隔符外的部分作为字段。
FPAT是取得匹配的字符部分作为字段。它是gawk提供的一个高级功能。
FPAT根据指定的正则来全局匹配record,然后将所有匹配成功的部分组成 1、2... ,不会修改 $0 。

Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA

其中逗号分隔每个字段,但双引号包围的是一个字段整体,即使其中有逗号。
这时使用FPAT来划分各字段比使用FS要方便的多。

echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
awk '
    BEGIN{FPAT="[^,]*|(\"[^\"]*\")"}
    {
        for (i=1;i<NF;i++){
            print "<"$i">"
        } 
    }
'

最后,patsplit()函数和FPAT的功能一样。

检查字段分隔的方式

有FS、FIELDWIDTHS、FPAT三种获取字段的方式,可使用 PROCINFO 数组来确定本次使用何种方式获得字段。
PROCINFO是一个数组,记录了awk进程工作时的状态信息。

if(PROCINFO["FS"]=="FS"){
    ...FS spliting...
} else if(PROCINFO["FPAT"]=="FPAT"){
    ...FPAT spliting...
} else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
    ...FIELDWIDTHS spliting...
}

修改字段或NF值的联动效应

注意下面的分割和计算两词:分割表示使用FS(field Separator),计算表示使用预定义变量OFS(Output Field Separator)。

  1. 修改 0 ,将使用 FS 重新分割字段,所以会影响1、$2...
  2. 修改 1、2 ,将根据 1 到NF 来重新计算 $0
    • 即使是 1 =1 这样的原值不变的修改,也一样会重新计算 $0
  3. 为不存在的字段赋值,将新增字段并按需使用空字符串填充中间的字段,并使用 OFS 重新计算 $0
    • awk '{(NF+2)=5;print0}' OFS='-' a.txt
  4. 增加NF值,将使用空字符串新增字段,并使用 OFS 重新计算 $0
    • awk '{NF+=3;print $0}' OFS='-' a.txt
  5. 减小NF值,将丢弃一定数量的尾部字段,并使用 OFS 重新计算 $0
    • awk '{NF-=3;print $0}' OFS='-' a.txt

关于$0

当读取一条record之后,将原原本本地被保存到 $0 当中。

awk '{print $0}' a.txt

但是,只要出现了上面所说的任何一种导致 0 重新计算的操作,都会立即使用OFS去重建0 。
换句话说,没有导致 0 重建,0就一直是原原本本的数据,所以指定OFS也无效。

awk '{print $0}' OFS="-" a.txt # OFS此处无效

当 $0 重建后,将自动使用OFS重建,所以即使没有指定OFS,它也会采用默认值(空格)进行重建。

awk '{$1=$1;print $0}' a.txt # 输出时将以空格分隔各字段
awk '{print $0;$1=$1;print $0}' OFS="-" a.txt

如果重建 $0 之后,再去修改OFS,将对当前行无效,但对之后的行有效。所以如果也要对当前行生效,需要再次重 建。

# OFS对第一行无效
awk '{$4+=10;OFS="-";print $0}' a.txt
# 对所有行有效
awk '{$4+=10;OFS="-";$1=$1;print $0}' a.txt

关注 0 重建是一个非常有用的技巧。\ 例如,下面通过重建0 的技巧来实现去除行首行尾空格并压缩中间空格

$ echo " a b c d " | awk '{$1=$1;print}'
a b c d
$ echo " a b c d " | awk '{$1=$1;print}' OFS="-" 
a-b-c-d

awk数据筛选示例

筛选行

# 1.根据行号筛选
awk 'NR==2' a.txt # 筛选出第二行
awk 'NR>=2' a.txt # 输出第2行和之后的行

# 2.根据正则表达式筛选整行
awk '/qq.com/' a.txt # 输出带有qq.com的行 
awk '$0 ~ /qq.com/' a.txt # 等价于上面命令
awk '/^[^@]+$/' a.txt # 输出不包含@符号的行 
awk '!/@/' a.txt # 输出不包含@符号的行

# 3.根据字段来筛选行
awk '($4+0) > 24{print $0}' a.txt # 输出第4字段大于24的行 
awk '$5 ~ /qq.com/' a.txt # 输出第5字段包含qq.com的行

# 4.将多个筛选条件结合起来进行筛选
awk 'NR>=2 && NR<=7' a.txt
awk '$3=="male" && $6 ~ /^170/' a.txt 
awk '$3=="male" || $6 ~ /^170/' a.txt

# 5.按照范围进行筛选 flip flop
# pattern1,pattern2{action}
awk 'NR==2,NR==7' a.txt # 输出第2到第7行 
awk 'NR==2,$6 ~ /^170/' a.txt

处理字段

修改字段时,一定要注意,可能带来的联动效应:即使用OFS重建$0。

awk 'NR>1{$4=$4+5;print $0}' a.txt
awk 'NR>1{$6=$6"*";print $0}' a.txt

awk运维面试试题

从ifconfig命令的结果中筛选出除了lo网卡外的所有IPv4地址。

# 1.法一:
ifconfig | awk '/inet / && !($2 ~ /^127/){print $2}'
# 2.法二:
ifconfig | awk 'BEGIN{RS=""}!/lo/{print $6}'
# 3.法三:
ifconfig | awk 'BEGIN{RS="";FS="\n"}!/lo/{$0=$2;FS=" ";$0=$0;print $2}'

awk工作流程

参考自: man awk 的"AWK PROGRAM EXECUTION"段。

man --pager='less -p ^"AWK PROGRAM EXECUTION" awk

执行步骤

  1. 解析 -v var=val... 选项中的变量赋值
  2. 编译awk源代码为awk可解释的内部格式,包括-v的变量
  3. 执行BEGIN代码段
  4. 根据输入记录分隔符RS读取文件(根据ARGV数组的元素决定要读取的文件),如果没有指定文件,则从标准 输入中读取文件,同时执行main代码段
    • 如果文件名部分指定为 var=val 格式,则声明并创建变量,此阶段的变量在BEGIN之后声明,所以 BEGIN中不可用,main代码段可用
    • 每读取一条记录:
      • 都将设置NR、FNR、RT、$0等变量
      • (默认)根据输入字段分隔符FS切割字段,将各字段保存到 1、2... 中
      • 测试main代码段的pattern部分,如果测试成功则执行action部分
  5. 执行END代码段
上一篇 下一篇

猜你喜欢

热点阅读