SHELL脚本之常用文本处理命令⑤awk


既是linux中的一条命令,也是一种编程语言,用于处理数据和生成报表。awk由三个人名组成,本身没什么特别的意义。

语法

  • awk [options] '[pattern]{actions}' [var1=value1 var2=value2 ...] file ...
  • awk [options] -f scripts_filename [var1=value1 var2=value2 ...] file ...

options 选项

  • -F:field separator,字符分割符,默认为空白符(包括空格、制表符等)
  • -f SCRIPTS_FILE:用于执行指定的脚本文件
  • -v:变量声明
    • awk -v a=1 -v b=2 '{print a,b}'

pattern 模式

  • BEGIN
    • 指定在第一条记录被处理前发生的动作
    • 用于初始化语句、内置或用户变量的定义,初始化格式
  • END
    • 指定在最后一条记录被处理后发生的动作
    • 用于总结输出
  • 处理中(空)
    • 对所有行应用指令
    • /RegExp/:处理正则表达式匹配到的行
    • /RegExp1/, /RegExp2/:指定一个行的范围,如/^a/, /^b/
    • 条件表达式
      • 关系运算符:>,<,>=,<=,==,!=,用于字符串或数字的标胶
      • 正则匹配表达式:~,!~,如awk -F: ‘$1 ~ /^a.*e$/{print $1,$3}’

action 操作

  • 变量或数组赋值操作
    • var=value
    • name="alice"
    • 数组
      • a["age"]="haha"
      • a[1]="xxx"
  • 格式化输出
    • 指定字段用 $N 表示,$0 为整行内容,引用变量不需要加 $
    • pirnt
    • printf:跟c语言用法差不多,可以用 %s%d 等占位
  • 内置函数
    • sub
    • substr
  • 控制流命令
    • if/else
    • for
    • while

awk脚本文件格式

  • 一行中多条语句用分号分隔:action1;action2

  • 每条语句在不同行,可以不想需要分号分隔

  • 如果操作跟在某个模式后面,它的左大括号就必须与该模式在同一行(像java的习惯写法)

    BEGIN {
    	action1
    	action2
    }
    
  • 注释:#

记录和字段

  • 记录定义:Record,文件的每一行被称为一条记录,以换行符分隔
    • 输入数据具有固定格式的结构
    • 不是无休止的字符串
    • 内置变量:
      • $0:当前行内容,当前记录的内容
      • ORS:output record separator,记录输出分割符,默认为换行
      • RS:record separator,记录分割符,默认为换行
      • NR:number of records,当前处理的总记录编号
      • FNR:file’s number of record,当前处理的文件的记录编号,awk可以处理多个文件,当读取一个新文件时 FNR 会重新开始计数,所以 FNRNR
  • 字段定义:Field,每条记录由多个字段组成,字段之间用分割符分开
    • 内置变量:
      • OFS:output field separator,字段输出分割符,默认为空格
      • FS:field separator,字段分割符,默认为空白符
      • NF:number of fields,当前记录字段数
    • 字段分割:
      • 通过 -F 选项来修改 FS 的指:awk -F: ‘{print $1}’
      • 同时使用多个字段分割符,将分割符放入[]中:echo -e “a:b\tc” | awk -F’[:\t]’ ‘{print $1, $3}’

格式化输出

print

  • 参数可以是变量、计算表达式、字符串变量
  • 字符串必须用双引号引起来:echo 300 2 3 4 | awk '{print "hello" }'
  • 参数之间用逗号分隔:echo 300 2 3 4 | awk '{print $1,$2 }'
  • 输出可以被重定向:echo 300 2 3 4 | awk '$1 * $2 > 500{print $3+$4 >> "/tmp/test" }'
  • 输入和输出可以通过管道:echo 300 2 3 4 | awk '$1 * $2 > 500{print $3+$4 | "grep 7" }'
    • 注意这里是将所有该操作的结果通过管道
  • 转义序列
    • \n:换行符
    • \t:制表符
    • \r:回车
    • \047:八进制值47

printf

  • 返回给标准输出一个带有格式的字符串:f,format

  • 不会在行尾自动换行

  • 包含一个加“”的控制字符串

  • 修饰符

    • -:左对齐,默认右对齐
    • +:数字带正负符号(+,-)
  • 格式说明转换符 %

    • %c:character,字符
    • %s:string,字符串
    • %d:digit,整数
    • %f:fload,浮点数
[root@localhost ~]# echo alice 23 01 |awk ' {printf "The name is: %-15s ID is %8d\n",$1,$3}'
The name is: alice           ID is        1

练习

  • test文件内容:
Mary   2143 78 84 77
Jack    2321 66 78 45
Tom     2122 48 77 71
Mike    2537 87 97 95
Bob     2415 40 57 62
  • 输出成绩表:
Lineno.   Name    No.    Math   English   Computer    Total
------------------------------------------------------------
1        Mary    2143    78      84        77         239     
2        Jack    2321    66      78        45         189     
3        Tom     2122    48      77        71         196     
4        Mike    2537    87      97        95         279     
5        Bob     2415    40      57        62         159     
------------------------------------------------------------
Total:                   319     393       350                  
Avg:                     63.8    78.6      70                   
  • 通过 .awk
BEGIN {
	printf "%-10s%-10s%-10s%-10s%-10s%-10s%-10s\n","LNO","Name","No","Math","English","Computer","Total"
	printf "------------------------------------------------------------------\n"
}
{
	math+=$3;english+=$4;com+=$5;printf "%-10s%-10s%-10s%-10s%-10s%-10s%-10s\n",NR,$1,$2,$3,$4,$5,$3+$4+$5
}
END {
	printf "------------------------------------------------------------------\n"
	printf "%-30s%-10s%-10s%-10s\n","Total:",math,english,com
	printf "%-30s%-10s%-10s%-10s\n","Avg:",math/NR,english/NR,com/NR
}
  • 通过命令
awk 'BEGIN{math=0;eng=0;com=0;printf "Lineno.   Name    No.    Math   English   Computer    Total\n";printf "------------------------------------------------------------\n"}{math+=$3; eng+=$4; com+=$5;printf "%-8s %-7s %-7s %-7s %-9s %-10s %-7s \n",NR,$1,$2,$3,$4,$5,$3+$4+$5} END{printf "------------------------------------------------------------\n";printf "%-24s %-7s %-9s %-20s \n","Total:",math,eng,com;printf "%-24s %-7s %-9s %-20s \n","Avg:",math/NR,eng/NR,com/NR}' test

编程结构

关系表达式

  • < , <= , >, >= , == , !=
  • ~ 和 !~:匹配/不匹配操作符,用于对记录或字段的表达式匹配
    • awk '$1 ~ [Ll]ove{printf }' file
    • awk '$1 !~ [Ll]ove' file

复合模式

  • &&:逻辑与
    • echo | awk 'a>b && a!=0 {print a}' a=2 b=1
  • ||:逻辑或
    • echo | awk 'a>b || a!=0 {print a}' a=1 b=2
  • !:逻辑非
    • echo | awk '! a!=0 {print a}' a=0 b=2

条件赋值

  • var=(EXP)?var1:var2:其实就是三目表达式
    • echo| awk’{max=(a>b)? a:b; print max}’ a=1 b=2

范围模式

  • /RegExp1/,/RegExp2/
    • awk '/^a/,/^b/{print $0}' file

算数运算

  • +,-,*,/,%,^或**:加,减,乘,除,求余,求幂
  • ++,--:一元加减
  • =,+=,-=,*=,/=,%=,^=,**=:赋值运算符

函数

内置函数

字符串函数
  • sub(/RegExp/,"STR1"[,"STR2"]) FILENAME:substitute,替换

    • 在记录中查找能够匹配正则表达式的最长最靠左子串,然后用替换串STR1替换找到的子串
    • 如果指定了目标串就对目标串查找替换,不指定则对当前记录处理
    • 只对每行出现的第一个匹配进行替换
    • 例:搜索STR2中RegExp匹配的内容,替换成STR1
      • echo "abca" | awk '{sub(/a/,"b");print}'
      • echo "ab:ca" | awk -F: '{sub(/a/,"b",$1);print}'
  • gsub(/RegExp/,"STR1"[,"STR2"]) FILENAME:global substitute,全局替换,用法和sub一样

  • index("字符串","查找子串"):返回子串在字符串中第一次出现的位置,索引号从1开始计算,如无返回0

    • echo "ab:cd" | awk -F: '{print index($1,"a")}'
    • echo "ab:cd" | awk -F: '{print index($1,"b")}'
  • substr("字符串",开始位置,长度):返回从字符串指定位置开始的子串

    • echo "abcdef" | awk '{print substr($0,1,3)}'
  • match("字符串",/正则表达式/):返回正则表达式在字符串中出现的位置,如果未出现则返回0

    • 内置变量 RSTART 为匹配到的子串在字符串中的起始位置,下标从 1 开始

    • 内置变量 RLENGTH 为子串的长度

    • 可以通过 substr 提取出匹配到的子串

    • echo "abcdefABC" | awk '{match($0,/[[:upper:]]+/);print RSTART,RLENGTH;print substr($0,RSTART,RLENGTH)}'

      [root@localhost ~]# echo "abcdefABC" | awk '{match($0,/[[:upper:]]+/);print RSTART,RLENGTH;print substr($0,RSTART,RLENGTH)}'
      7 3
      ABC
      
  • split("字符串",数组名,"字段分隔符"):使用第三个参数指定的字段分割符将字符串拆分成一个数组

    • echo "18/01/31" | awk '{split($0,date,"/");print date[1],date[2],date[3]}'
  • length("字符串"):返回字符串的长度(以字符为单位)

  • blength("字符串"):返回字符串的长度(以字节为单位)

  • tolower("字符串"):字符串中每个大写字符将更改为小写

  • toupper("字符串"):字符串中每个小写字符将更改为大写

时间函数
  • mktime( YYYY MM DD HH MM SS):生成用一串数字表示的时间戳

    • awk 'BEGIN{tstamp=mktime("2013 01 04 12 12 12"); print tstamp}'
    [root@localhost ~]# awk 'BEGIN{tstamp=mktime("2013 01 04 12 12 12"); print tstamp;}'
    1357319532
    
  • systime():得到时间戳,返回从1970年1月1日开始到当前时间(不计闰年)的整秒数

  • strftime([格式 [, 时间戳]]):格式化时间输出,将时间戳转为时间字符串,省略时间戳表示当前时间

    [root@localhost ~]# awk 'BEGIN{tstamp=mktime("2013 01 04 12 12 12"); print strftime("%c", tstamp);}'
    2013年01月04日 星期五 12时12分12秒
    [root@localhost ~]# awk 'BEGIN{tstamp=mktime("2013 01 04 12 12 12"); print strftime("%c");}'
    2020年03月24日 星期二 05时29分42秒
    [root@localhost ~]# awk 'BEGIN{tstamp=mktime("2013 01 04 12 12 12"); print strftime();}'
    二 3月 24 05:32:04 EDT 2020
    
算数函数
  • int(x):取整
  • rand():返回随机数 n ,其中 0 ≤ n <1
  • sqrt(x):开平方
其他函数
  • sytem("CMD"):调用系统命令执行

自定义函数

# 格式
function_name(参数1,参数2,...){
	statements
    return expression
}

变量

用户自定义变量

  • 变量名由字母、数字、下划线组成,不能以数字开头
  • awk可以从上下文推导出它的数据类型
  • 字符串变量要括在“”中

内置变量

属性 说明
$0 当前记录(作为单个变量)
$1~$n 当前记录的第n个字段,字段间由FS分隔
FS 输入字段分隔符 默认是空格
OFS 输出字段分隔符 默认也是空格
RS 输入的记录分隔符,默认换行符
ORS 输出的记录分隔符,默认为换行符
NF 当前记录中的字段个数,就是有多少列
NR 已经读出的记录数,就是行号,从1开始
FNR 该文件当前记录数
ARGC 命令行参数个数
ARGV 命令行参数数组
FILENAME 当前输入文件的名字
IGNORECASE 如果为真,则进行忽略大小写的匹配
RSTART 被匹配函数匹配的字符串首
RLENGTH 被匹配函数匹配的字符串长度

重定向

输出重定向

  • >echo | awk '{print "abc" > "test" }
  • >>

输入重定向

  • getline
    • 如果得到一条记录,getline函数返回1,如果到达文件的末尾就返回0,如果出现错误,例如打开文件失败,就返回-1,可以结合到while等流控制语句使用
    • awk 'BEGIN{getline < "-";print}'
    • awk 'BEGIN{getline < "test.txt"; print $0}'
    • awk 'BEGIN{ while( getline line < "test" ){ print line } }'

管道

可以在awk打开一个管道,但同一时刻只能由一个管道存在:若第一个管道没有被关闭就使用第二个管道,则数据仍会传到第一个管道中,因为前一次管道里的数据和文件描述符(指针)还建立着连接。通过close()可关闭管道

  • awk '{print $1, $2 | "sort" }' test END {close("sort")}

条件语句

  • if/else

    if (expression) {
    	statement;
    	...
    }
    else if (expression) {
    	statement;
    	...
    }
    else {
    	statement;
    }
    

循环

  • while

    while (expression) {
    	statement;
    	...
    }
    
  • for

    for (i=1;i<=NF;i++){
    	statement;
    	...
    }
    
    for (i in Array){
    	# 注意这里取出来的i为Array的key
        print i,Array[i]
    }
    

程序控制

  • next:跳过当前记录,读取下一条记录
  • exit N:退出awk执行

数组

  • 下标可以是数字或字符串(相当于字典)
  • 数组元素不是顺序存储
  • 下标通常称为键(key)
    • 变量可以作为数组下标
    • 字段值可以作为数组下标
    • 循环遍历数组中的元素:for (i in Arrayname) {print Arrayname[i]}
      • 这里读出来的 i 是 key 而不是 value
  • 统计tcp的链接数
[root@localhost ~]# netstat -ant | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}'
LISTEN 	 12
ESTABLISHED 	 2
  • 分别统计不同ip的tcp链接
[root@localhost ~]# netstat -ant | awk '/^tcp/ {n=split($(NF-1),array,":");if(n<=2)++S[array[(1)]];else++S[array[(4)]];++s[$NF];++N} END {for(a in S){printf("%-20s %s\n", a, S[a]);++I}printf("%-20s %s\n","TOTAL_IP",I);for(a in s) printf("%-20s %s\n",a, s[a]);printf("%-20s %s\n","TOTAL_LINK",N);}'
*                    6
192.168.159.1        2
0.0.0.0              6
TOTAL_IP             3
LISTEN               12
ESTABLISHED          2
TOTAL_LINK           14
[root@localhost ~]# netstat -ant | awk '/^tcp/ {n=split($(NF-1),array,":");if(n<=2)++S[array[(1)]];else++S[array[(4)]];++s[$NF];++N} END {for(a in S){printf("%-20s %s\n", a, S[a]);++I}printf("%-20s %s\n","TOTAL_IP",I);for(a in s) printf("%-20s %s\n",a, s[a]);printf("%-20s %s\n","TOTAL_LINK",N);}'
*                    6
192.168.159.1        2
0.0.0.0              6
TOTAL_IP             3
LISTEN               12
ESTABLISHED          2
TOTAL_LINK           14

awk相关知识

发布了42 篇原创文章 · 获赞 2 · 访问量 918

猜你喜欢

转载自blog.csdn.net/weixin_42511320/article/details/105081966