awk--基本操作一

通过学习《awk精通》整理

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

what

awk是一个文本处理工具

awk基本用法

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

1. 按字符数量读取:每一次可以读取一个字符,或者多个字符,直到把整个文件读取完
         while read -n 1 char;do echo $char;done 

awk用法入门

 awk 'awk_program' a.txt
  • a.txt是awk要读取的文件,可以是0个文件或一个文件,也可以多个文件
    • 如果不给定任何文件,但又需要读取文件,则表示从标准输入中读取
  • 单引号 包围的是awk代码,也称为awk程序
    • 尽量使用单引号,因为在awk中经常使用 符号在Shell是变量符号,如果使用双引号包围 awk代码,
      则 会脱离Shell的魔掌,使得$符号留给了awk去解析
  • awk程序中,大量使用大括号,大括号表示代码块,代码块中间可以之间连用,代码块内部的多个语句需使用分 号";"分隔
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代码块:

  • 在读取文件之前执行,且执行一次
  • 在BEGIN代码块中,无法使用 $0 或其它一些特殊变量

END代码块:

  • 在读取文件完成之后执行,且执行一次
  • 有END代码块,必有要读取的数据(可以是标准输入)
  • END代码块中可以使用$0等一些特殊变量,只不过这些特殊变量保存的是最后一轮awk循环的数据

main代码块:

  • 读取文件时循环执行,(默认情况)每读取一行,就执行一次main代码块
  • 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
      • awk '{print $0}' a.txt
    • 省略代码块{action} ,等价于 {print} 即输出所有行
      • awk '/Alice/' a.txt 等价于awk '/Alice/{print $0}' a.txt
    • 省略代码块中的 ,表示对筛选的行什么都不做
      • awk '/Alice/{}' a.txt
    • pattern{action}任何一部分都可以省略
      + awk '' a.txt
    
  • 多个 pattern{action} 可以直接连接连用

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为单个字符:直接使用该字符来分割记录
  • RS为多个字符:将其当做正则表达式,只要匹配正则表达式的符号,都用来分割记录
    • 设置预定义变量IGNORECASE为非零值,正则匹配时表示忽略大小写
    • 兼容模式下,只有首字符才生效,不会使用正则模式去分割记录
      特殊的RS值用来解决特殊读取需求:
    • 按段落读取:RS=''
    • RS='\0' 一次性读取所有数据,但有些特殊文件中包含了空字符 \0
    • RS="^$" 真正的一次性读取所有数据,因为非空文件不可能匹配成功
    • RS='\n+'按行读取,但忽略所有空行
# 按段落读取: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。

  • NR:所有文件的行号计数器
  • FNR:是各个文件的行号计数器
awk '{print NR}' a.txt a.txt
awk '{print FNR}' a.txt a.txt

详细的分段字段分割

awk读取每一条记录之后,会将其赋值给 1 3 N ,同时将划分的字段数量赋值给预定义变量NF。

引用字段的方式

$N 引用字段:

  • N=0 :即 $0 ,引用记录本身
  • 0
  • N>NF :表示引用不存在的字段,返回空字符串 N<0 :报错
    可使用变量或计算的方式指定要获取的字段序号。
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 :字段分隔符

  • FS为单个字符时,该字符即为字段分隔符
  • FS为多个字符时,则采用正则表达式模式作为字段分隔符
  • 特殊的,也是FS默认的情况,FS为单个空格时,将以连续的空白(空格、制表符、换行符)作为字段分隔符
  • 特殊的,FS为空字符串""时,将对每个字符都进行分隔,即每个字符都作为一个字段
  • 设置预定义变量IGNORECASE为非零值,正则匹配时表示忽略大小写(只影响正则,所以FS为单字时无影响)
  • 如果record中无法找到FS指定的分隔符(例如将FS设置为"\n"),则整个记录作为一个字段,即 0 相 等

FIELDWIDTHS

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

  • FIELDWIDTHS="3 5 6 9" 表示第一个字段3字符,第二字段5字符...
  • FIELDWIDTHS = "8 1:5 6 2:33"表示:
    • 第一个字段读8个字符
    • 然后跳过1个字符再读5个字符作为第二个字段
    • 然后读6个字符作为第三个字段
    • 然后跳过2个字符在读33个字符作为第四个字段(如果不足33个字符,则读到结尾)
  • FIELDWIDTHS="2 3 *" :
    • 第一个字段2个字符
    • 第二个字段3个字符
    • 第三个字段剩余所有字符 星号只能放在最后,且只能单独使用,表示剩余所有
  • 设置该变量后,FS失效
  • 之后再设置FS或FPAT,
    示例1:
# 没取完的字符串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   [email protected]     18023394012
2   Alice   female  24   [email protected]  18084925203
3   Tony    male    21   [email protected]    17048792503
4   Kevin   male    21                  17023929033
5   Alex    male    18   [email protected]    18185904230
6   Andy    female  22   [email protected]    18923902352
7   Jerry   female  25   [email protected]  18785234906
8   Peter   male    20   [email protected]     17729348758
9   Steven  female  23   [email protected]    15947893212
10  Bruce   female  27   [email protected]   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,然后将所有匹配成功的部分组成 2... ,不会修改 $0 。

  • awk 'BEGIN{FPAT="[0-9]+"}{print $3"-"}' a.txt
  • 之后再设置FS或FPAT,该变量将失效
    FPAT常用于字段中包含了字段分隔符的场景。例如,CSV文件中的一行数据如下:
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"
        } 
    }
'

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

检查字段分隔的方式

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

  • PROCINFO["FS"]=="FS",表示使用FS分割获取字段
  • PROCINFO["FPAT"]=="FPAT" ,表示使用FPAT匹配获取字段
  • PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS",表示使用FIELDWIDTHS分割获取字段
    例如:
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. 修改 1、$2...
  2. 修改 2 ,将根据 NF 来重新计算 $0
    • 即使是 1 这样的原值不变的修改,也一样会重新计算 $0
  3. 为不存在的字段赋值,将新增字段并按需使用空字符串填充中间的字段,并使用 OFS 重新计算 $0
    • awk '{0}' 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 。
换句话说,没有导致 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 的技巧来实现去除行首行尾空格并压缩中间空格

$ 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切割字段,将各字段保存到 2... 中
      • 测试main代码段的pattern部分,如果测试成功则执行action部分
  5. 执行END代码段

你可能感兴趣的:(awk--基本操作一)