精通awk系列
安装新版本gawk
awk有很多种版本,例如nawk、gawk。gawk是GNU awk,它的功能很丰富。
本教程采用的是gawk 4.2.0版本,4.2.0版本的gawk是一个比较大的改版,新支持的一些特性非常好用,而在低于4.2.0版本时这些语法可能会报错。所以,请先安装4.2.0版本或更高版本的gawk。
查看awk版本
[root@localhost ~]# awk --version GNU Awk 4.0.2 Copyright (C) 1989, 1991-2012 Free Software Foundation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/.
这里以安装gawk 4.2.0为例。
[root@localhost ~]# wget --no-check-certificate https://mirrors.tuna.tsinghua.edu.cn/gnu/gawk/gawk-4.2.0.tar.gz [root@localhost ~]# tar -zxf gawk-4.2.0.tar.gz [root@localhost ~]# cd gawk-4.2.0 [root@localhost gawk-4.2.0]# ./configure --prefix=/usr/local/gawk4.2 && make && make install [root@localhost gawk-4.2.0]# ln -fs /usr/local/gawk4.2/bin/gawk /usr/bin/awk [root@localhost gawk-4.2.0]# awk --version [root@localhost gawk-4.2.0]# gawk --version 此时,调用awk将调用新版本的gawk,调用gawk将调用旧版本的gawk
本系列的awk教程中,将大量使用到如下示例文件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 bbb@189.com 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
读取文件的几种方式
读取文件有如下几种常见的方式:
下面使用Shell的read命令来演示前4种读取文件的方式(第五种按字节数读取的方式read不支持)。
按字符数量读取
read的-n选项和-N选项可以指定一次性读取多少个字符。
[root@localhost ~]# read -n 1 data <a.txt 只读一个字符。 [root@localhost ~]# echo $data I [root@localhost ~]# [root@localhost ~]# read -n 100 data < a.txt 读100个字符,但如果不足100字符时遇到换行符则停止读取 [root@localhost ~]# echo $data ID name gender age email phone [root@localhost ~]# read -N 100 data < a.txt [root@localhost ~]# echo $data ID name gender age email phone 1 Bob male 28 abc@qq.com 18023394012 2 强制读取100字符,遇到换行符也不停止
如果按照字符数量读取,直到把文件读完,则使用while循环,且将文件放在while结构的后面,而不能放在while循环的条件位置:
[root@localhost ~]# while read -N 3 data;do > echo "$data" > done <a.txt
按分隔符读取
read命令的-d选项可以指定读取文件时的分隔符。
[root@localhost ~]# read -d "m" data <a.txt [root@localhost ~]# echo $data ID na 一直读取,直到遇到字符m才停止,并将读取的数据保存到data变量中
如果要按分隔符读取并读完整个文件,则使用while循环:
[root@localhost ~]# while read -d "m" data ;do > echo "$data" > done <a.txt
按行读取
read默认情况下就是按行读取的,一次读取一行。
[root@localhost ~]# read line <a.txt [root@localhost ~]# echo $line ID name gender age email phone
如果要求按行读取完整个文件,则使用while循环:
[root@localhost ~]# while read line;do > echo "$line" > done <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 bbb@189.com 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
一次性读整个文件
要一次性读取完整个文件,有两种方式:
- 按照字符数量读取,且指定的字符数要大于文件的总大小
- 按分隔符读取,且指定的分隔符是文件中不存在的字符,这样的话会一直读取,因为找不到分隔符而读完整个文件
[root@localhost ~]# read -N 1000000 data <a.txt [root@localhost ~]# echo $data 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 bbb@189.com 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 指定超出文件大小的字符数量 [root@localhost ~]# read -d "_" data <a.txt [root@localhost ~]# echo $data 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 bbb@189.com 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 指定文件中不存在的字符作为分隔符
awk用法入门
[root@localhost ~]# awk 'awk_program' a.txt
awk示例:
#输出a.txt中的每一行 [root@localhost ~]# awk '{print $0}' 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 bbb@189.com 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 # 多个代码块,代码块中多个语句 # 输出每行之后还输出两行:hello行和world行 [root@localhost ~]# awk '{print $0}{print "hello";print "world"}' a.txt ID name gender age email phone hello world 1 Bob male 28 abc@qq.com 18023394012 hello world 2 Alice female 24 def@gmail.com 18084925203 hello world 3 Tony male 21 aaa@163.com 17048792503 hello world 4 Kevin male 21 bbb@189.com 17023929033 hello world 5 Alex male 18 ccc@xyz.com 18185904230 hello world 6 Andy female 22 ddd@139.com 18923902352 hello world 7 Jerry female 25 exdsa@189.com 18785234906 hello world 8 Peter male 20 bax@qq.com 17729348758 hello world 9 Steven female 23 bc@sohu.com 15947893212 hello world 10 Bruce female 27 bcbd@139.com 13942943905 hello world
对于awk '{print $0}' a.txt
,它类似于shell的while循环while read line;do echo "$line";done <a.txt
。awk隐藏了读取每一行的while循环,它会自动读取每一行,其中的{print $0}
对应于Shell的while循环体echo "$line"
部分。
下面再分析该awk命令的执行过程:
- 读取文件第一行(awk默认按行读取文件)
- 将所读取的行赋值给awk的变量
$0
,于是$0
中保存的就是本次所读取的行数据 - 进入代码块
{print $0}
并执行其中代码print $0
,即输出$0
,也即输出当前所读取的行 - 执行完本次代码之后,进入下一轮awk循环:继续读取下一行(第二行)
- 将第二行赋值给变量
$0
- 进入代码块执行
print $0
- 执行完代码块后再次进入下一轮awk循环,即读取第三行,然后赋值给
$0
,再执行代码块 - ...不断循环,直到读完文件所有数据...
- 将第二行赋值给变量
5.退出awk
BEGIN和END语句块
awk的所有代码(目前这么认为)都是写在语句块中的。
例如:
awk '{print $0}' a.txt awk '{print $0}{print $0;print $0}' a.txt
每个语句块前面可以有pattern,所以格式为:
pattern1{statement1}pattern2{statement3;statement4;...}
语句块可分为3类:BEGIN语句块、END语句块和main语句块。其中BEGIN语句块和END语句块都是的格式分别为BEGIN{...}
和END{...}
,而main语句块是一种统称,它的pattern部分没有固定格式,也可以省略,main代码块是在读取文件的每一行的时候都执行的代码块。
分析下面三个awk命令的执行结果
[root@localhost ~]# awk 'BEGIN{print "我在前面"}{print $0}' a.txt 我在前面 ID name gender age email phone 1 Bob male 28 abc@qq.com 18023394012 ................................ [root@localhost ~]# awk 'END{print "我在后面"}{print $0}' a.txt ................................ 9 Steven female 23 bc@sohu.com 15947893212 10 Bruce female 27 bcbd@139.com 13942943905 我在后面 [root@localhost ~]# awk 'BEGIN{print "我在前面"}{print $0}END{print "我在后面"}' a.txt 我在前面 ...................................... 10 Bruce female 27 bcbd@139.com 13942943905 我在后面
根据上面3行命令的执行结果,可总结出如下有关于BEGIN、END和main代码块的特性:
BEGIN代码块:
- 在读取文件之前执行,且执行一次
- 在BEGIN代码块中,无法使用
$0
或其它一些特殊变量
main代码块:
- 读取文件时循环执行,(默认情况)每读取一行,就执行一次main代码块
- main代码块可有多个
END代码块:
- 在读取文件完成之后执行,且执行一次
- 有END代码块,必有要读取的数据(可以是标准输入)
- END代码块中可以使用
$0
等一些特殊变量,只不过这些特殊变量保存的是最后一轮awk循环的数据
awk命令行结构和语法结构
awk命令行结构
awk [ -- ] program-text file ... (1) awk -f program-file [ -- ] file ... (2) awk -e program-text [ -- ] file ... (3)
其中:
awk语法结构
awk语法结构即awk代码部分的结构。
awk的语法充斥着pattern{action}
的模式,它们称为awk rule。
例如:
[root@localhost ~]# awk '/^[0-9]/{$1>5;print $1} /Alice/{print "Alice"} END{print "hello"}' a.txt
上面示例中,有BEGIN语句块,有END语句块,还有2个main代码块,两个main代码块都使用了正则表达式作为pattern。
关于awk的语法:
- 多个
pattern{action}
可以直接连接连用 - action中多个语句如果写在同一行,则需使用分号分隔
- pattern部分用于筛选行,action表示在筛选通过后执行的操作
- pattern和action都可以省略
- 省略
pattern
,等价于对每一行数据都执行action- 例如:
awk '{print $0}' a.txt
- 例如:
- 省略代码块
{action}
,等价于{print}
即输出所有行- 例如:
awk '/Alice/' a.txt
等价于awk '/Alice/{print $0}' a.txt
- 例如:
- 省略代码块中的
action
,表示对筛选的行什么都不做- 例如:
awk '/Alice/{}' a.txt
- 例如:
pattern{action}
任何一部分都可以省略- 例如:
awk '' 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如何读取文件
wk读取输入文件时,每次读取一条记录(record)(默认情况下按行读取,所以此时记录就是行)。每读取一条记录,将其保存到$0
中,然后执行一次main代码段。
awk '{print $0}' a.txt
如果是空文件,则因为无法读取到任何一条记录,将导致直接关闭文件,而不会进入main代码段。
[root@localhost ~]# touch x.log 创建一个空文件 [root@localhost ~]# awk '{print "hello world"}' x.log
可设置表示输入记录分隔符的预定义变量RS(Record Separator)来改变每次读取的记录模式。
[root@localhost ~]# awk 'BEGIN{RS="\n"}{print $0}' a.txt [root@localhost ~]# awk 'BEGIN{RS="m"}{print $0}' a.txt
RS通常设置在BEGIN代码块中,因为要先于读取文件就确定好RS分隔符。
RS指定输入记录分隔符时,所读取的记录中是不包含分隔符字符的。例如RS="a"
,则$0
中一定不可能出现字符a。
特殊的RS值用来解决特殊读取需求:
RS=""
:按段落读取RS="\0"
:一次性读取所有数据,但有些特殊文件中包含了空字符\0
RS="^$"
:真正的一次性读取所有数据,因为非空文件不可能匹配成功RS="\n+"
:按行读取,但忽略所有空行
示例:
按段落读取:RS=“” [root@localhost ~]# awk 'BEGIN{RS=""}{print $0"------"}' 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 bbb@189.com 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------ 一次性读取所有数据:RS='\0' RS="^$" [root@localhost ~]# awk 'BEGIN{RS='\0'}{print $0"------"}' a.txt ID name gender age email phone 1 Bob male 28 abc@qq.com 18------ 23394------ 12 2 Alice female 24 def@gmail.com 18------ 849252------ 3 3 Tony male 21 aaa@163.com 17------ 487925------ 3 4 Kevin male 21 bbb@189.com 17------ 23929------ 33 5 Alex male 18 ccc@xyz.com 181859------ 423------ 6 Andy female 22 ddd@139.com 189239------ 2352 7 Jerry female 25 exdsa@189.com 187852349------ 6 8 Peter male 2------ bax@qq.com 17729348758 9 Steven female 23 bc@sohu.com 15947893212 1------ Bruce female 27 bcbd@139.com 139429439------ 5 ------ [root@localhost ~]# awk 'BEGIN{RS="^$"}{print $0"------"}' 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 bbb@189.com 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 ------ 忽略空行:RS='\n+' [root@localhost ~]# awk 'BEGIN{RS="\n+"}{print $0"------"}' 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 bbb@189.com 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------ 忽略大小写:预定义变量IGNORECASE设置为非0值 [root@localhost ~]# 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
[root@localhost ~]# awk 'BEGIN{RS="(fe)?male"}{print RT}' a.txt male female male male male female female male female female
两种行号:NR和FNR
在读取每条记录之后,将其赋值给$0,同时还会设置NR、FNR、RT。
- NR:所有文件的行号计数器
- FNR:是各个文件的行号计数器
[root@localhost ~]# awk '{print NR}' a.txt a.txt 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [root@localhost ~]# awk '{print FNR}' a.txt a.txt 1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 8 9 10 11
详细分析awk字段分割
awk读取每一条记录之后,会将其赋值给$0
,同时还会对这条记录按照预定义变量FS划分字段,将划分好的各个字段分别赋值给$1 $2 $3 $4...$N
,同时将划分的字段数量赋值给预定义变量NF。
引用字段的方式
$N
引用字段:
N=0
:即$0
,引用记录本身0<N<=NF
:引用对应字段N>NF
:表示引用不存在的字段,返回空字符串N<0
:报错
可使用变量或计算的方式指定要获取的字段序号。
[root@localhost ~]# awk '{n = 5;print $n}' a.txt [root@localhost ~]# awk '{print $(2+2)}' a.txt [root@localhost ~]# 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"),则整个记录作为一个字段,即
$1
和$0
相等
[root@localhost ~]# awk -F":" '{print $1}' /etc/passwd [root@localhost ~]# awk 'BEGIN{FS=":"}{print $1}' /etc/passwd 字段分隔符指定为单个字符 [root@localhost ~]# awk 'BEGIN{FS=" +|@"}{print $1,$2,$3,$4,$5,$6}' a.txt 字段分隔符指定为正则表达式
划分字段方式(二):FIELDWIDTHS
指定预定义变量FIELDWIDTHS按字符宽度分割字段,这是gawk提供的高级功能。在处理某字段缺失时非常好用。
用法:
示例1:
没取完的字符串DDD被丢弃,且NF=3 [root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="2 3 2"}{print $1,$2,$3,$4}' <<<"AABBBCCDDDD" AA BBB CC 字符串不够长度时无视 [root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="2 3 2 100"}{print $1,$2,$3,$4"-"}' <<<"AABBBCCDDDD" AA BBB CC DDDD- *号取剩余所有,NF=3 [root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="2 3 *"}{print $1,$2,$3}' <<<"AABBBCCDDDD" AA BBB CCDDDD 字段数多了,则取完字符串即可,NF=2 [root@localhost ~]# 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 bbb@189.com 17023929033 5 Alex male 18 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字符 [root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"} NR>1{print "<"$1">","<"$2">","<"$3">","<"$4">","<"$5">","<"$6">"}' a1.txt <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> <bbb@189.com > <17023929033> <5 > <Alex > <male > <18> < > <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>
[root@localhost ~]# awk 'BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"} NR>1{if($5 ~ /^ +$/){print $0}}' a1.txt 5 Alex male 18 18185904230 # 如果email为空,则输出它
划分字段方式(三):FPAT
FS是指定字段分隔符,来取得除分隔符外的部分作为字段。
FPAT是取得匹配的字符部分作为字段。它是gawk提供的一个高级功能。
FPAT根据指定的正则来全局匹配record,然后将所有匹配成功的部分组成$1、$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
[root@localhost ~]# awk 'BEGIN{FPAT="[0-9]+"}{print $3"-"}' a.txt - 18023394012- 18084925203- 163- 189- 18185904230- 139- 189- 17729348758- 15947893212- 139-
其中逗号分隔每个字段,但双引号包围的是一个字段整体,即使其中有逗号。
这时使用FPAT来划分各字段比使用FS要方便的多。
[root@localhost ~]# echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' | \ > awk ' \ > BEGIN{FPAT="[^,]*|(\"[^\"]*\")"} \ > { \ > for (i=1;i<NF;i++){ \ > print "<"$i">" > } \ > }' <Robbins> <Arnold> <"1234 A Pretty Street, NE"> <MyTown> <MyState> <12345-6789>
最后,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)。
- 修改
$0
,将使用FS
重新分割字段,所以会影响$1、$2...
- 修改
$1、$2
,将根据$1
到$NF
等各字段来重新计算$0
- 即使是
$1 = $1
这样的原值不变的修改,也一样会重新计算$0
- 即使是
- 为不存在的字段赋值,将新增字段并按需使用空字符串填充中间的字段,并使用
OFS
重新计算$0
awk 'BEGIN{OFS="-"}{$(NF+2)=5;print $0}' a.txt
- 增加NF值,将使用空字符串新增字段,并使用
OFS
重新计算$0
awk 'BEGIN{OFS="-"}{NF+=3;print $0}' a.txt
- 减小NF值,将丢弃一定数量的尾部字段,并使用
OFS
重新计算$0
awk 'BEGIN{OFS="-"}{NF-=3;print $0}' a.txt
[root@localhost ~]# awk 'BEGIN{OFS="-"}{$(NF+2)=5;print $0}' a.txt ID-name-gender-age-email-phone--5 1-Bob-male-28-abc@qq.com-18023394012--5 2-Alice-female-24-def@gmail.com-18084925203--5 3-Tony-male-21-aaa@163.com-17048792503--5 4-Kevin-male-21-bbb@189.com-17023929033--5 5-Alex-male-18-ccc@xyz.com-18185904230--5 6-Andy-female-22-ddd@139.com-18923902352--5 7-Jerry-female-25-exdsa@189.com-18785234906--5 8-Peter-male-20-bax@qq.com-17729348758--5 9-Steven-female-23-bc@sohu.com-15947893212--5 10-Bruce-female-27-bcbd@139.com-13942943905--5 [root@localhost ~]# awk 'BEGIN{OFS="-"}{NF+=3;print $0}' 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-bbb@189.com-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--- [root@localhost ~]# awk 'BEGIN{OFS="-"}{NF-=3;print $0}' a.txt ID-name-gender 1-Bob-male 2-Alice-female 3-Tony-male 4-Kevin-male 5-Alex-male 6-Andy-female 7-Jerry-female 8-Peter-male 9-Steven-female 10-Bruce-female
关于$0
当读取一条record之后,将原原本本地被保存到$0
当中。
awk '{print $0}' a.txt
换句话说,没有导致$0
重建,$0
就一直是原原本本的数据,所以指定OFS也无效。
[root@localhost ~]# awk 'BEGIN{OFS="-"}{print $0}' a.txt # OFS此处无效 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 bbb@189.com 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
当$0
重建后,将自动使用OFS重建,所以即使没有指定OFS,它也会采用默认值(空格)进行重建。
[root@localhost ~]# awk '{$1=$1;print $0}' 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 bbb@189.com 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
[root@localhost ~]# awk '{print $0;$1=$1;print $0}' OFS="-" a.txt
ID name gender age email phone
ID-name-gender-age-email-phone
1 Bob male 28 abc@qq.com 18023394012
1-Bob-male-28-abc@qq.com-18023394012
2 Alice female 24 def@gmail.com 18084925203
2-Alice-female-24-def@gmail.com-18084925203
3 Tony male 21 aaa@163.com 17048792503
3-Tony-male-21-aaa@163.com-17048792503
4 Kevin male 21 bbb@189.com 17023929033
4-Kevin-male-21-bbb@189.com-17023929033
5 Alex male 18 ccc@xyz.com 18185904230
5-Alex-male-18-ccc@xyz.com-18185904230
6 Andy female 22 ddd@139.com 18923902352
6-Andy-female-22-ddd@139.com-18923902352
7 Jerry female 25 exdsa@189.com 18785234906
7-Jerry-female-25-exdsa@189.com-18785234906
8 Peter male 20 bax@qq.com 17729348758
8-Peter-male-20-bax@qq.com-17729348758
9 Steven female 23 bc@sohu.com 15947893212
9-Steven-female-23-bc@sohu.com-15947893212
10 Bruce female 27 bcbd@139.com 13942943905
10-Bruce-female-27-bcbd@139.com-13942943905
如果重建$0
之后,再去修改OFS,将对当前行无效,但对之后的行有效。所以如果也要对当前行生效,需要再次重建。
[root@localhost ~]# awk '{$4+=10;OFS="-";print $0}' a.txt OFS对第一行无效 ID name gender 10 email phone 1-Bob-male-38-abc@qq.com-18023394012 2-Alice-female-34-def@gmail.com-18084925203 3-Tony-male-31-aaa@163.com-17048792503 4-Kevin-male-31-bbb@189.com-17023929033 5-Alex-male-28-ccc@xyz.com-18185904230 6-Andy-female-32-ddd@139.com-18923902352 7-Jerry-female-35-exdsa@189.com-18785234906 8-Peter-male-30-bax@qq.com-17729348758 9-Steven-female-33-bc@sohu.com-15947893212 10-Bruce-female-37-bcbd@139.com-13942943905 对所有行有效 [root@localhost ~]# awk '{$4+=10;OFS="-";$1=$1;print $0}' a.txt ID-name-gender-10-email-phone 1-Bob-male-38-abc@qq.com-18023394012 2-Alice-female-34-def@gmail.com-18084925203 3-Tony-male-31-aaa@163.com-17048792503 4-Kevin-male-31-bbb@189.com-17023929033 5-Alex-male-28-ccc@xyz.com-18185904230 6-Andy-female-32-ddd@139.com-18923902352 7-Jerry-female-35-exdsa@189.com-18785234906 8-Peter-male-30-bax@qq.com-17729348758 9-Steven-female-33-bc@sohu.com-15947893212 10-Bruce-female-37-bcbd@139.com-13942943905
关注$0
重建是一个非常有用的技巧。
例如,下面通过重建$0
的技巧来实现去除行首行尾空格并压缩中间空格:
[root@localhost ~]# echo " a b c d " | awk '{$1=$1;print}' a b c d [root@localhost ~]# 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
处理字段
从ifconfig命令的结果中筛选出除了lo网卡外的所有IPv4地址。
[root@localhost ~]# ifconfig | awk '/inet / && !($2 ~ /^127/){print $2}' 192.168.1.205
awk工作流程
参考自:man awk
的"AWK PROGRAM EXECUTION"段。
man --pager='less -p ^"AWK PROGRAM EXECUTION"' awk
执行步骤:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?