美文网首页
6、Shell_awk

6、Shell_awk

作者: 沙砾丶ye | 来源:发表于2019-12-26 08:40 被阅读0次

一、awk简介

awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。

数据可以来自标准输入、一个或多个文件,或其它命令的输出。

支持用户自定义函数和动态正则表达式等先进功能,是linux/unix
下的一个强大编程工具。

在命令行中使用,但更多是作为脚本来使用。

awk的处理文本和数据的方式是这样的,它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。如果没有指定处理动作,则把匹配的行显示到标准输出(屏幕),如果没有指定模式,则所有被操作所指定的行都被处理。

awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。

gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。

二、awk的两种形式语法格式

awk [options] 'commands' filenames
awk [options] -f awk-script-file filenames

options:
-F 对于每次处理的内容,可以指定一个子定义的分隔符,默认的分隔符是空白字符(空格或 tab 键 )
command:

BEGIN{}                        {}               END{}

处理所有内容之前的动作       处理内容中的动作   处理所有内容之后的动作

示例

 awk 'BEGIN{print "----开始处理了---"} {print "ok"} END{print "----都处理完毕---"}' /etc/hosts
----开始处理了---
ok
ok
ok
----都处理完毕---

BEGIN{} 通常用于定义一些变量,例如 BEGIN{FS=":";OFS="---"}

三、awk工作原理

awk -F: '{print 1,3}' /etc/passwd
(1)awk,会处理文件的每一个行,每次处理时,使用一行作为输入,并将这一行赋给内部变量$0,每一行也可称为一个记录,以换行符结束

(2)然后,行被:(默认为空格或制表符)分解成字段(或称为域),每个字段存储在已编号的变量中,从$1开始,
最多达100个字段

(3)awk如何知道用空白字符来分隔字段的呢? 因为有一个内部变量FS来确定字段分隔符。初始时,FS赋为空白字符

(4)awk打印字段时,将以内置的方法使用 print 函数打印,awk 在打印出的字段间加上空格。这个空格是内部的一个变量 OFS 输出字段的分隔符, 逗号 , 会和 OFS 进行映射,通过 OFS 可以控制这个输出分隔符的值。

(5)awk输出之后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕

=====================================

四、记录与字段相关内部变量:

man awk

0 : awk变量0 保存当前正在处理的行内容
NR : 当前正在处理的行是 awk 总共处理的行号。
FNR: 当前正在处理的行在其文件中的行号。
NF :每行被处理时的总字段数
$NF: 当前处理行的分隔后的最后一个字段的值
FS : 输入行时的字段分隔符,默认空格

awk 'BEGIN{FS=":"} {print $1,$3}' /etc/passwd

OFS : 输出字段分隔符,默认是一个 空格

awk 'BEGIN{FS=":"; OFS="+++"} /^root/{print $1,$2,$3,$4}' /etc/passwd

ORS 输出记录分隔符, 默认是换行符.
示例
将文件每一行合并为一行

ORS默认输出一条记录应该回车,但是这里是加了一个空格

awk 'BEGIN{ORS="  "} {print $0}' /etc/passwd

五、格式化输出:

awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' /etc/passwd
awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd

%s 字符类型
%d 十进制整数
%f 浮点类型
%-15s占15字符 - 表示左对齐,默认是右对齐
printf 默认不会在行尾自动换行,加 \n

六、awk模式和动作

任何 awk 语句都由 模式 和 动作 组成。

模式部分 决定动作语句何时触发及触发事件。
如果省略模式部分,动作将时刻保持执行状态。

模式可以是任何条件语句或复合语句或正则表达式。

正则表达式:

将整行进行正则匹配(包含):

就是当前处理的行有没有包含 指定的模式(书写的正则表达式)
/正则/ 正则需要写在双斜线内

awk '/^root/' /etc/passwd
awk '$0 ~ /^root/' /etc/passwd
awk '!/root/' /tec/ passwd
awk '$0 !~ /^root/' /etc/passwd

将某一字段进行正则匹配:

可以使用的匹配操作符(~ 和 !~)
字段 ~ /正则/

awk -F: '$1 ~ /^alice/' /etc/passwd
awk -F: '$NF !~ /bash$/' /etc/passwd

实现 字符串的完全相等需要使用 ==

字符串需要使用双引号
!= 表示不等于

awk -F: '$NF == "/bin/bash"' /etc/passwd
awk -F: '$1 == "root"' /etc/passwd

比较表达式:

比较表达式采用对文本进行比较,只有当条件为真,才执行指定的动作。
比较表达式使用关系运算符,用于比较数字与字符串。

关系运算符有
< 小于 例如 x<y

大于 x>y
<= 小于或等于 x<=y
== 等于 x==y
!= 不等于 x!=y
= 大于等于 x>=y
示例

awk -F: '$3 == 0' /etc/passwd
awk -F: '$3 < 10' /etc/passwd


df -P | grep '/' |awk '$4 > 25000'

条件表达式:

awk -F: '$3>300 {print $0}' /etc/passwd
awk -F: '{ if($3>300) print $0 }' /etc/passwd
awk -F: '{ if($3>300) {print $0} }' /etc/passwd
awk -F: '{ if($3>300) {print $3} else{print $1} }' /etc/passwd

算术运算:+, -, *, /, %(模: 取余), (幂:23)

可以在模式中执行计算,awk都将按浮点数方式执行算术运算

awk -F: '$3 * 10 > 500' /etc/passwd
awk -F: '{ if($3*10>500){print $0} }' /etc/passwd

逻辑操作符和复合模式

&& 逻辑与, 相当于 并且
||逻辑或,相当于 或者
! 逻辑非 , 取反

awk -F: '$1~/root/ && $3<=15' /etc/passwd
awk -F: '$1~/root/ || $3<=15' /etc/passwd
awk -F: '!($1~/root/ || $3<=15)' /etc/passwd
➜  ~ cat passwd
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:20:2:daemon:/sbin:/sbin/nologin
root:x:20:2:daemon:/sbin:/sbin/nologin
➜  ~ awk -F: '!($1~/root/ || $3<=15){print $0}' passwd
daemon:x:20:2:daemon:/sbin:/sbin/nologin
➜  ~ awk -F: '($1~/root/ || $3<=15){print $0}' passwd
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin
root:x:20:2:daemon:/sbin:/sbin/nologin

范围模式, 模式之间用逗号 , 隔开
使用语法是: 起始表达式, 终止表达式

下面的意思是: 从开头是 bin 的行开始匹配成功一直到含有 adm 的行结束匹配
也就是 开头是 bin 的行到含有 adm 的行 的所有内容都符合匹配条件。


➜  ~ awk -F: '/^bin/,/adm/ {print $0 }' /etc/passwd
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin

awk 正则示例:

匹配开头是 bin 的或者开头是 root 的行

➜  ~ awk -F: '/^(bin|root)/' /etc/passwd
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin

指定多个分隔符:[]

echo "a b|c d| ||||e | |" |awk -F'[ |]' '{print $10}'
echo "a b|c d| ||||e | |" |awk -F'[ |]+' '{print $5}'

注意: 正则中,中括号内的任意字符均视为普通字符, 比如 . * 都被看做是 普通字符。
例如:

$ echo "a.b*c" |awk -F'[.*]' '{print $1, $2,$3}'
a b c

七、awk 脚本编程

条件判断

if语句

格式 { if (表达式) {语句; 语句; ...}}

awk -F: '{if($3==0) {print $1 " is administrator."}}' /etc/passwd
输出: root is administrator.

# 统计系统级别用户的数量
awk -F: '{if($3>0 && $3<1000){count++;}} END{print count}' /etc/passwd 
输出: 22

if...else语句

格式 {if(表达式){语句;语句;...}else{语句;语句;...}}

awk -F: '{if($3==0){print $1} else {print $7}}' /etc/passwd

awk -F: '{ if($3==0){count++} else{i++} } END{print "管理员个数: "count "系统用户数: "i}' /etc/passwd
输入:
管理员个数: 1系统用户数: 24

awk -F: '{ if($3==0){count++} else{i++} } END{print "管理员个数: "count ; print  "系统用户数: "i}' /etc/passwd
输出:
管理员个数: 1
系统用户数: 24

if...else if...else语句

格式
{if(表达式1) {语句;语句;...} else if (表达式2) {语句;语句;...} else if(表达式3){语句;语句;...} else {语句;语句;...} }

awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print i; print k; print j}' /etc/passwd
输出:
1
2
22

awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员个数: "i; print "普通用个数: "k; print "系统用户: "j}' /etc/passwd
输出:
管理员个数: 1
普通用个数: 2
系统用户: 22

awk使用外部变量:
方法一:awk参数-v(推荐使用,易读)

[root@tianyun ~]# echo "unix script" |awk -v var="bash" 'gsub(/unix/,var)'
bash script

[root@tianyun ~]# awk -v user=root -F: '$1 == **user**' /etc/passwd
root:x:0:0:root:/root:/bin/bash

gsub 使 AWK 中的内置函数,功能使搜索替换
'gsub(/unix/,var) 中的意思是搜索 unix 字符串,替换为 var 变量对应的值

方法二:在双引号的情况下使用

[root@tianyun ~]# var="bash"
[root@tianyun ~]# echo "unix script" |awk "gsub(/unix/,\"$var\")"
bash script

方法三:在单引号的情况下使用(逼不得已使用)

[root@tianyun ~]# var="bash"
[root@tianyun ~]# echo "unix script" |awk 'gsub(/unix/,"'"$var"'")'
bash script

练习

[root@tianyun ~]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/cl-root 2.8T 246G 2.5T 9% /
tmpfs 24G 20K 24G 1% /dev/shm
/dev/sda2 1014M 194M 821M 20% /boot

[root@tianyun ~]# df -h |awk '{ if(int($5)>5){print $6":"$5} }'
/:9%
/boot:20%

[root@tianyun ~]# i=10
[root@tianyun ~]# df -h |awk '{ if(int($5)>'''$i'''){print $6":"$5} }'
/boot:20%

shark AWK 练习

AWK

$ cat gy.txt
机构号  机构名称        省别    尾箱号  尾箱状态   领用柜员号   领用柜员姓名    币种    余额
11007eee        北京东城区街支行   北京    03      未领用           156     19001.68
11007eee        北京东城区街支行   北京    03      未领用           840     2672.00
11007eee        北京东城区街支行   北京    04      未领用           156     7261.31
11007eee        北京朝阳区路支行   北京    02      未领用           156     161490.08
11007eee        北京朝阳区路支行   北京    03      未领用           840     19711.00
11007eee        山西煤矿区路支行   山西    03      未领用           156     282370.23
11007eee        北京朝阳区路支行   北京    04      未领用           840     24551.00
11007eee        江苏开发区区路支行 江苏    04    领用   1078  shark 156     372689.36
11007eee        北京朝阳区行      北京    02      未领用            156     304516.23
 awk '{printf "%-8s %-8s %-8s\n",$1,$2,$3}' 

过滤记录
过滤条件为:

 awk '$3=="江苏" && $5 == "领用" {print $6,$7}' qf.txt

其中的 == 为比较运算符。其他比较运算符:!=, >, <, >=, <=

各种过滤记录的方式:

awk ' $4 > 3 {print $0}' gy.txt

如果我们需要表头的话,我们可以引入内建变量NR:

awk '$3=="山东" && $5=="未领用"  || NR==1 ' gy.txt

再加上格式化输出:

$ awk '$3=="山东" && $5=="未领用" || NR==1 {printf "%-10s %-21s %-6s\n",$1,$2,$3}' gy.txt

内建变量

awk的一些内建变量:
0 当前记录(这个变量中存放着正处理的当前行内容)n 当前记录的第n个字段,字段间由FS分隔
FS 输入字段分隔符 默认是空格或Tab
NF 当前记录中的字段个数,就是有多少列
$NF 最后一列
NR 已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。
RS 输入的记录分隔符, 默认为换行符
OFS 输出字段分隔符, 默认也是空格
ORS 输出的记录分隔符,默认为换行符
FILENAME 当前输入文件的名字
FNR 当前记录数,与NR不同的是,这个值会是各个文件自己的行号

怎么使用呢,比如:我们如果要输出行号:

$ awk '$3=="山东" && $5=="未领用" || NR==1 {printf "%-6s %-5s %-12s %-15s %-2s \n",NR, FNR, $1,$2,$3}' gy.txt

指定分隔符

$  awk  'BEGIN{FS=":"} {print $1,$3,$6}' /etc/passwd

上面的命令也等价于:
-F的意思就是指定分隔符

$ awk  -F: '{print $1,$3,$6}' /etc/passwd

指定多个分隔符:

awk -F '[;:]'

再来看一个以\t作为分隔符输出的例子(下面使用了/etc/passwd文件,这个文件是以:分隔的)

$ awk  -F: '{print $1,$3,$6}' OFS="\t" /etc/passwd
root    0       /root
bin     1       /bin
daemon  2       /sbin
adm     3       /var/adm
lp      4       /var/spool/lpd
sync    5       /sbin

字符串匹配
示例:

awk '$2 ~ /平阴县/ || NR==1 {print NR, $1,$4}' gy.txt

~ 表示模式开始。
/ / 中是模式。这就是一个正则表达式的匹配。

其实awk可以像grep一样,进行逐行匹配,就像这样:

$ awk '/.*县/' gy.txt

我们可以使用 “/平阴县|济阳县/” 来匹配 平阴县 或者 济阳县 :

$ awk '$2 ~ /平阴县|济阳县/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" gy.txt

再来看看模式取反的例子:

$ awk '$2 !~ /平阴县/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" gy.txt

或是:

$ awk '!/平阴县/' gy.txt

折分文件

awk拆分文件很简单,使用重定向就好了。

下面这个例子,是按第 3 例分隔文件,相当的简单(其中的NR!=1表示不处理表头)。

$ awk 'NR!=1{print > $3}' gy.txt
$ ls
gy.txt  北京     山东     山西     江苏 

你也可以把指定的列输出到文件:

awk 'NR!=1{print $2,$4,$5 > $3}' gy.txt

再复杂一点:(注意其中的if-else-if语句,可见awk其实是个脚本解释器)

$ awk 'NR!=1 {if($3 ~ /北京|山东/) print > "1.txt";
else if($3 ~ /山西/) print > "2.txt";
else print > "3.txt" }' gy.txt
 
$ ls ?.txt
1.txt  2.txt  3.txt

统计
下面的命令计算所有的 txt 文件的文件大小总和。

$ ps aux | awk 'NR!=1 {a[$1]+=$6;} END { for(i in a) print i ", " a[i]"KB";}'

awk脚本

BEGIN 和 END,这两个关键字意味着执行前和执行后的意思,语法如下:

BEGIN { 这里面放的是执行前的语句 }

END {这里面放的是处理完所有的行后要执行的语句 }

{这里面放的是处理每一行时要执行的语句}

示例:

假设有这么一个文件(学生成绩表):

$ cat score.txt
Marry   2143 78 84 77
Jack    2321 66 78 45
Tom     2122 48 77 71
Mike    2537 87 97 95
Bob     2415 40 57 62

我们的AWK脚本如下

$ cat cal.awk
#!/bin/awk -f
#处理每行之前
BEGIN {
    math = 0
    english = 0
    computer = 0
 
    printf "NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL\n"
    printf "---------------------------------------------\n"
}

#处理中
{
    math+=$3
    english+=$4
    computer+=$5
    printf "%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5
}

#处理后
END {
    printf "---------------------------------------------\n"
    printf "  TOTAL:%10d %8d %8d \n", math, english, computer
    printf "AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR
}

我们来看一下执行结果:(也可以这样运行 ./cal.awk score.txt)

$ awk -f cal.awk score.txt
NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL
---------------------------------------------
Marry  2143     78       84       77      239
Jack   2321     66       78       45      189
Tom    2122     48       77       71      196
Mike   2537     87       97       95      279
Bob    2415     40       57       62      159
---------------------------------------------
  TOTAL:       319      393      350
AVERAGE:     63.80    78.60    70.00

环境变量
怎么和环境变量交互:(使用-v参数和ENVIRON,使用ENVIRON的环境变量需要export)

$ x=5
$ y=10
$ export y
 
$ echo $x $y
5 10
 
$ awk -v val=$x '{print $1, $2, $3, $4+val, $5+ENVIRON["y"]}' OFS="\t" score.txt
Marry   2143    78      89      87
Jack    2321    66      83      55
Tom     2122    48      82      81
Mike    2537    87      102     105
Bob     2415    40      62      72

最后,我们再来看几个小例子

#从file文件中找出长度大于80的行
awk 'length>80' file
 
#按连接数查看客户端IP
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr
 
#打印99乘法表
seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'

<meta charset="utf-8">

关于其中的一些知识点可以参看gawk的手册:

内建变量,参看:http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din-Variables

流控方面,参看:http://www.gnu.org/software/gawk/manual/gawk.html#Statements

内建函数,参看:http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din

正则表达式,参看:http://www.gnu.org/software/gawk/manual/gawk.html#Regexp

"""

# 访问TOP 20 的IP
16348 58.16.183.52
awk  '$9==200 {print $1}'  2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head -20


访问状态码为20X的  TOP 10的IP
2097 125.70.184.99
2000 183.225.69.158
awk  '$9 > 200 && $9 < 300{print $1}' 2018-05-10-0000-2330_app.log  | sort |uniq -c |sort -r |head


访问TOP 20 的url
250563 http://app.xxx.com/update/2018-04-04/dangbeimarket_4.0.9_162_znds.apk
awk  '$9 == 200{print $7,$9}' 2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head -20

访问状态码为20X的  TOP 10的url
248786 http://app.znds.com/update/2018-04-04/dangbeimarket_4.0.9_162_znds.apk
awk  '$9 > 200 && $9 < 300{print $7,$9}' 2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head


访问次数超过1W的IP
58.16.184.247
58.16.183.52
awk  '{print $1}' 2018-05-10-0000-2330_app.log| sort |uniq -c |sort -r |awk '$1 > 10000 {print $1}'


访问状态码为404的  TOP 10的url
1017 http://app.xxx.com/update/fixedaddress/kuaisou_qcast.apk.md5
awk  '$9 == 404 {print $1}' 2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head
"""

相关文章

  • 6、Shell_awk

    一、awk简介 awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。 数据可以来自标准输入、...

  • shell_awk

    shell-awk awk简介 awk是一种编程语言,用于在linux下对文本和数据处理数据可以来自标准输入、一个...

  • shell_awk

    一、awk简介 awk是一种编程语言,用在linux/unix下对文本和数据进行出路。数据可以来自标准输入、一个或...

  • Shell_awk

    一、awk简介 awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。 数据可以来自标准输入、...

  • #知识体系精深营#六月+12次作业+第20组2小组+ynqj_a

    6-1 6-1 6-2 6-2 6-3 6-3 6-4 6-4 6-5 6-5 6-6 6-6 6-7 6-7 6...

  • 无标题文章

    1 2 2 3 5 6 6 6 6 6 6 8 3 6

  • 6+6=6

    【九洲芳文】 我不再记得安妮,这句话在一段时间内反复出现在我脑子里,侵蚀着我的神经,仿佛在强迫我遗忘记忆里某些很重...

  • 6/6

    今天又被骂了,会议又出错了,心情更糟了,即使有人开始分担工作,但是还是很晚下班。昨晚去运动,后来也没自己的份,这样...

  • 6/6

    口号(如:父母的高度就是孩子的起点)#孩子第二个30天目标:早睡加早起,培养清理整理书包学习桌面的能力 妈妈第二个...

  • 6/6

    通过这些年的实验,都证实了一点,自己所想的所要的都慢慢一点一点的实现了。所以,只要敢想,敢做,我们最终会成为自己想...

网友评论

      本文标题:6、Shell_awk

      本文链接:https://www.haomeiwen.com/subject/aefkoctx.html