|
安装新版本gawk
awk有很多种版本,例如nawk、gawk。gawk是GNU awk,它的功能很丰富。
本教程采用的是gawk 4.2.0版本,4.2.0版本的gawk是一个比较大的改版,新支持的一些特性非常好用,而在低于4.2.0版本时这些语法可能会报错。所以,请先安装4.2.0版本或更高版本的gawk。
查看awk版本这里以安装gawk 4.2.0为例。- # 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.编译,并执行安装目录为/usr/local/gawk4.2
- ./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教程中,将大量使用到如下示例文件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
复制代码 读取文件的几种方式
读取文件有如下几种常见的方式:
- 按字符数量读取:每一次可以读取一个字符,或者多个字符,直到把整个文件读取完
- 按照分隔符进行读取:一直读取直到遇到了分隔符才停止,下次继续从分隔的位置处向后读取,直到读完整个文件
- 按行读取:每次读取一行,直到把整个文件读完
- 它是按照分隔符读取的一种特殊情况:将分隔符指定为了换行符\n
- 一次性读取整个文件
- 是按字符数量读取的特殊情况
- 也是按分隔符读取的特殊情况
- 按字节数量读取:一次读取指定数量的字节数据,直到把文件读完
下面使用Shell的read命令来演示前4种读取文件的方式(第五种按字节数读取的方式read不支持)。
按字符数量读取
read的-n选项和-N选项可以指定一次性读取多少个字符。- # 只读一个字符
- read -n 1 data <a.txt
- # 读100个字符,但如果不足100字符时遇到换行符则停止读取
- read -n 100 data < a.txt
- # 强制读取100字符,遇到换行符也不停止
- read -N 100 data < a.txt
复制代码 可以修改OFMT,来自定义数值转换为字符串时的格式:- # 正确
- while read -N 3 data;do
- echo "$data"
- done <a.txt
- # 错误
- while read -N 3 data < a.txt;do
- echo "$data"
- done
复制代码 printf
- # 一直读取,直到遇到字符m才停止,并将读取的数据保存到data变量中
- read -d "m" data <a.txt
复制代码 格式化字符:
修饰符:均放在格式化字符的前面- while read -d "m" data ;do
- echo "$data"
- done <a.txt
复制代码 sprintf()
sprintf()采用和printf相同的方式格式化字符串,但是它不会输出格式化后的字符串,而是返回格式化后的字符串。所以,可以将格式化后的字符串赋值给某个变量。- # 从a.txt中读取第一行保存到变量data中
- read line <a.txt
复制代码 重定向输出
print[f] something | Shell_Cmd时,awk将创建一个管道,然后启动Shell命令,print[f]产生的数据放入管道,而命令将从管道中读取数据。- while read line;do
- echo "$line"
- done <a.txt
复制代码 print[f] something |& Shell_Cmd时,print[f]产生的数据交给Coprocess。之后,awk再从Coprocess中取回数据。这里的|&有点类似于能够让Shell_Cmd后台异步运行的管道。
stdin、stdout、stderr
awk重定向时可以直接使用/dev/stdin、/dev/stdout和/dev/stderr。还可以直接使用某个已打开的文件描述符/dev/fd/N。
例如:- # 指定超出文件大小的字符数量
- read -N 1000000 data <a.txt
- echo "$data"
- # 指定文件中不存在的字符作为分隔符
- read -d "_" data <a.txt
- echo "$data"
复制代码 awk变量
awk的变量是动态变量,在使用时声明。
所以awk变量有3种状态:
- 未声明状态:称为untyped类型
- 引用过但未赋值状态:unassigned类型
- 已赋值状态
引用未赋值的变量,其默认初始值为空字符串或数值0。
在awk中未声明的变量称为untyped,声明了但未赋值(只要引用了就声明了)的变量其类型为unassigned。
gawk 4.2版提供了typeof()函数,可以测试变量的数据类型,包括测试变量是否声明。除了typeof(),还可以使用下面的技巧进行检测:- # 输出a.txt中的每一行
- awk '{print $0}' a.txt
- # 多个代码块,代码块中多个语句
- # 输出每行之后还输出两行:hello行和world行
- awk '{print $0}{print "hello";print "world"}' a.txt
复制代码 变量赋值
awk中的变量赋值语句也可以看作是一个有返回值的表达式。
例如,a=3赋值完成后返回3,同时变量a也被设置为3。
基于这个特点,有两点用法:
- 可以x=y=z=5,等价于z=5 y=5 x=5
- 可以将赋值语句放在任意允许使用表达式的地方
- x != (y = 1)
- awk 'BEGIN{print (a=4);print a}'
问题:a=1;arr[a+=2] = (a=a+6)是怎么赋值的,对应元素结果等于?arr[3]=7。但不要这么做,因为不同awk的赋值语句左右两边的评估顺序有可能不同。
awk中声明变量的位置
awk中使用Shell变量
要在awk中使用Shell变量,有三种方式:
1.在-v选项中将Shell变量赋值给awk变量- awk '{print $0}' a.txt
- awk '{print $0}{print $0;print $0}' a.txt
复制代码 -v选项是在awk工作流程的第一阶段解析的,所以-v选项声明的变量在BEGIN{}、END{}和main代码段中都能直接使用。
2.在非选项型参数位置处使用var=value格式将Shell变量赋值给awk变量- pattern1{statement1}pattern2{statement3;statement4;...}
复制代码 非选项型参数设置的变量不能在BEGIN代码段中使用。
3.直接在awk代码部分暴露Shell变量,交给Shell解析进行Shell的变量替换- awk 'BEGIN{print "我在前面"}{print $0}' a.txt
- awk 'END{print "我在后面"}{print $0}' a.txt
- awk 'BEGIN{print "我在前面"}{print $0}END{print "我在后面"}' a.txt
复制代码 这种方式最灵活,但可读性最差,可能会出现大量的引号。
数据类型
gawk有两种基本的数据类型:数值和字符串。在gawk 4.2.0版本中,还支持第三种基本的数据类型:正则表达式类型。
数据是什么类型在使用它的上下文中决定:在字符串操作环境下将转换为字符串,在数值操作环境下将转换为数值。这和自然语言中的一个词语、一个单词在不同句子内的不同语义是一样的。
隐式转换:
- 算术加0操作可转换为数值类型
- "123" + 0返回数值123
- " 123abc" + 0转换为数值时为123
- 无效字符串将转换成0,例如"abc"+3返回3
- 连接空字符串可转换为字符串类型
- awk [ -- ] program-text file ... (1)
- awk -f program-file [ -- ] file ... (2)
- awk -e program-text [ -- ] file ... (3)
复制代码 显式转换:
- 数值->字符串:
- CONVFMT或sprintf():功能等价。都是指定数值转换为字符串时的格式
- awk '
- BEGIN{n=3}
- /^[0-9]/{$1>5{$1=333;print $1}
- /Alice/{print "Alice"}
- END{print "hello"}
- ' a.txt
- # 等价的单行式:
- awk 'BEGIN{n=3} /^[0-9]/{$1>5{$1=333;print $1} /Alice/{print "Alice"} END{print "hello"}' a.txt
复制代码- # 特殊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模式
复制代码 awk字面量
awk中有3种字面量:字符串字面量、数值字面量和正则表达式字面量。
数值字面量
- 整数、浮点数、科学计数
- 105、105.0、1.05e+2、1050e-1
- awk内部总是使用浮点数方式保存所有数值,但用户在使用可以转换成整数的数值时总会去掉小数点
- 数值12.0面向用户的值为12,12面向awk内部的值是12.0000000...0
算术运算
- touch x.log # 创建一个空文件
- awk '{print "hello world"}' x.log
复制代码 赋值操作(优先级最低):- # RS="\n" 、 RS="m"
- awk 'BEGIN{RS="\n"}{print $0}' a.txt
- awk 'BEGIN{RS="m"}{print $0}' a.txt
复制代码 疑惑:b = 6;print b += b++输出结果?可能是12或13。不同的awk的实现在评估顺序上不同,所以不要用这种可能产生歧义的语句。
字符串字面量
awk中的字符串都以双引号包围,不能以单引号包围。
字符串连接(串联):awk没有为字符串的串联操作提供运算符,可以直接连接或使用空格连接。- # 按段落读取: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
复制代码 注意:字符串串联虽然方便,但是要考虑串联的优先级。例如下面的:- awk '{print NR}' a.txt a.txt
- awk '{print FNR}' a.txt a.txt
复制代码 正则表达式字面量
普通正则:
- /[0-9]+/
- 匹配方式:"str" ~ /pattern/或"str" !~ /pattern/
- 匹配结果返回值为0(匹配失败)或1(匹配成功)
- 任何单独出现的/pattern/都等价于$0 ~ /pattern/
- if(/pattern/)等价于if($0 ~ /pattern/)
- 坑1:a=/pattern/等价于将$0 ~ /pattern/的匹配返回值(0或1)赋值给a
- 坑2:/pattern/ ~ $1等价于$0 ~ /pattern/ ~ $1,表示用$1去匹配0或1
- 坑3:/pattern/作为参数传给函数时,传递的是$0~/pat/的结果0或1
- 坑4.坑5.坑6...
强类型的正则字面量(gawk 4.2.0才支持):
gawk支持的正则
- awk '{n = 5;print $n}' a.txt
- awk '{print $(2+2)}' a.txt # 括号必不可少,用于改变优先级
- awk '{print $(NF-3)}' a.txt
复制代码 gawk不支持正则修饰符,所以无法直接指定忽略大小写的匹配。
如果想要实现忽略大小写匹配,则可以将字符串先转换为大写、小写再进行匹配。或者设置预定义变量IGNORECASE为非0值。- # 字段分隔符指定为单个字符
- awk -F":" '{print $1}' /etc/passwd
- awk 'BEGIN{FS=":"}{print $1}' /etc/passwd
- # 字段分隔符指定为正则表达式
- awk 'BEGIN{FS=" +|@"}{print $1,$2,$3,$4,$5,$6}' a.txt
复制代码 awk布尔值
在awk中,没有像其它语言一样专门提供true、false这样的关键字。
但它的布尔值逻辑非常简单:
- # 没取完的字符串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
复制代码 awk中比较操作
strnum类型
awk最基本的数据类型只有string和number(gawk 4.2.0版本之后支持正则表达式类型)。但是,对于用户输入数据(例如从文件中读取的各个字段值),它们理应属于string类型,但有时候它们看上去可能像是数值(例如$2=37),而有时候有需要这些值是数值类型。
注意,strnum类型只针对于awk中除数值常量、字符串常量、表达式计算结果外的数据。例如从文件中读取的字段$1、$2、ARGV数组中的元素等等。- 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
复制代码 大小比较操作
比较操作符:- # 字段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
复制代码 比较规则:- Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA
复制代码 简单来说,string优先级最高,只要string类型参与比较,就都按照string的比较方式,所以可能会进行隐式的类型转换。
其它时候都采用num类型比较。- echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
- awk '
- BEGIN{FPAT="[^,]*|("[^"]*")"}
- {
- for (i=1;i<NF;i++){
- print "<"$i">"
- }
- }
- '
复制代码 对于相同优先级的运算符,通常都是从左开始运算,但下面2种例外,它们都从右向左运算:
- if(PROCINFO["FS"]=="FS"){
- ...FS spliting...
- } else if(PROCINFO["FPAT"]=="FPAT"){
- ...FPAT spliting...
- } else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
- ...FIELDWIDTHS spliting...
- }
复制代码 再者,注意print和printf中出现的>符号,这时候它表示的是重定向符号,不能再出现优先级比它低的运算符,这时可以使用括号改变优先级。例如:流程控制语句
注:awk中语句块没有作用域,都是全局变量。- awk 'BEGIN{OFS="-"}{print $0}' a.txt # OFS此处无效
复制代码 代码块
- awk '{$1=$1;print $0}' a.txt # 输出时将以空格分隔各字段
- awk '{print $0;$1=$1;print $0}' OFS="-" a.txt
复制代码 if...else
- # OFS对第一行无效
- awk '{$4+=10;OFS="-";print $0}' a.txt
- # 对所有行有效
- awk '{$4+=10;OFS="-";$1=$1;print $0}' a.txt
复制代码 搞笑题:妻子告诉程序员老公,去买一斤包子,如果看见卖西瓜的,就买两个。结果是买了两个包子回来。- $ 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
复制代码- # 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
复制代码 switch...case
- awk 'NR>1{$4=$4+5;print $0}' a.txt
- awk 'BEGIN{OFS="-"}NR>1{$4=$4+5;print $0}' a.txt
- awk 'NR>1{$6=$6"*";print $0}' a.txt
复制代码 awk 中的switch分支语句功能较弱,只能进行等值比较或正则匹配。
各分支结尾需使用break来终止。- # 1.法一:多条件筛选
- ifconfig | awk '/inet / && !($2 ~ /^127/){print $2}'
- # 2.法二:按段落读取,然后取IPv4字段
- ifconfig | awk 'BEGIN{RS=""}!/lo/{print $6}'
- # 3.法三:按段落读取,每行1字段,然后取IPv4字段
- ifconfig | awk 'BEGIN{RS="";FS="\n"}!/lo/{$0=$2;FS=" ";$0=$0;print $2}'
复制代码 分支穿透:- man --pager='less -p ^"AWK PROGRAM EXECUTION"' awk
复制代码 while和do...while
- if( (getline) <= 0 ){...}
- if((getline) < 0){...}
- if((getline) > 0){...}
复制代码 while先判断条件再决定是否执行statements,do...while先执行statements再判断条件决定下次是否再执行statements。- # next
- exec 9<> filename
- while read -u 9 line;do
- ...code...
- continue # next
- ...code... # 这部分代码在本轮循环当中不再执行
- done
- # getline
- while read -u 9 line;do
- ...code...
- read -u 9 line # getline
- ...code...
- done
复制代码 再比如,按数组元素值的字符大小来比较。- awk '/^1/{print;getline;print;exit}' a.txt
复制代码 再比如,对元素值按数值升序比较,且相等时再按第一个字段ID进行数值降序比较。- awk '/^1/{print;if((getline)<=0){exit};print}' a.txt
复制代码 上面使用的arr[x,y]来存储额外信息,下面使用arr[x][y]多维数组的方式来存储额外信息实现同样的排序功能。此外,gawk还提供了两个内置函数asort()和asorti()来对数组进行排序。
awk ARGC和ARGV
预定义变量ARGV是一个数组,包含了所有的命令行参数。该数组使用从0开始的数值作为索引。
预定义变量ARGC初始时是ARGV数组的长度,即命令行参数的数量。
ARGV数组的数量和ARGC的值只有在awk刚开始运行的时候是保证相等的。- awk '
- /^1/{
- if((getline var)<=0){exit}
- print var
- print $0"--"$2
- }' a.txt
复制代码 awk读取文件是根据ARGC的值来进行的,有点类似于如下伪代码形式:
[code]while(i=1;i |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|