shell三剑客:grep、sed、awk
cut
-c:字符数来截取 character
-f:字段来截取 field
-d:指定分隔符 默认是tab
awk其实可以看做一种编程语言。
awk的来源:三个人名:
Alfred Aho,Peter Weinberger,Brian Kernighan
gawk (gnu awk)
awk命令工作原理
awk的数据字段变量:
awk的用-F来指定分隔符
awk ‘BEGIN{commands}pattern{commands}END{commands}’ file1
注意:这里的commands指的是awk里面的子命令,并不是shell中的mkdir、ls等命令!!!
例子:
# cat passwd|awk -F: 'BEGIN{print "############"}$3<100{print $1,$3,$6}END{print "@@@@@@@@@@@@@@"}'
执行结果:
############
root 0 /root
dbus 81 /
rpc 32 /var/cache/rpcbind
rpcuser 29 /var/lib/nfs
haldaemon 68 /
postfix 89 /var/spool/postfix
mysql 27 /var/lib/mysql
@@@@@@@@@@@@@@
语法:awk -F 分隔符‘/模式/{动作}’ 输入文件
例1:只有模式时,相当于grep
# cat passwd|awk '/liu/'
liu1:x:508:508::/home/liu1:/bin/bash
liu2:x:509:509::/home/liu2:/bin/bash
例2:只有动作时,就直接执行动作。
# who|awk '{print $2}'
tty1
pts/0
# who
root tty1 2016-06-24 23:20
root pts/0 2016-06-24 23:21 (liupeng.lan)
例3:输出以h开头的行的第一列和第七列。
# cat passwd|awk -F: '/^h/{print $1,$7}'
haldaemon /sbin/nologin
例4:显示不是以h开头的行的第一列和第七列
awk -F: '/^[^h]/{print $1,$7}' /etc/passwd
例5:以:或者/作为分隔符显示第1列和第10列
awk -F'[:/]' '{print $1,$10}' /etc/passwd
正则表达式和bash一致
例1:在$2里查找匹配/pts/的,有就输出$1。
# who | awk '$2 ~ /pts/{print $1}‘
例2:输出uid为两位的用户名和uid。
# cat passwd|awk -F: '$3 ~/\<..\>/{print $1,$3}'
dbus 81
rpc 32
rpcuser 29
haldaemon 68
postfix 89
mysql 27
例3:输出1~100能被5整除的或者以1开头的数字。
#seq 100 | awk '$1 % 5 == 0 || $1 ~ /^1/{print $1}'
例4:显示uid大于等于500并且家目录在/home下同时shell为bash结尾的用户名,uid,家目录,shell。
# cat passwd|awk -F: '$3>=500&&$6 ~/^\/home/&&$7 ~/bash/{print $1,$3,$6,$7}'
aaa 500 /home/aaa /bin/bash
stua6 501 /home/stua6 /bin/bash
stuv1 502 /home/stuv1 /bin/bash
stuv2 503 /home/stuv2 /bin/bash
练习1:查找出用户名里包含liu的用户,输出用户名和uid及shell。
答:
# cat passwd|awk -F':' '$1 ~/liu/{print $1,$3,$6}'
liu1 508 /home/liu1
liu2 509 /home/liu2
liu3 510 /home/liu3
liu4 511 /home/liu4
liu5 512 /home/liu5
liu 539 /home/liu
练习2:查找出/etc/passwd文件里用户名包含liu并且使用bash的用户。
# cat passwd|awk -F':' '$1 ~/liu/&&$7 ~/bash/{print $1,$7}'
liu1 /bin/bash
liu2 /bin/bash
liu3 /bin/bash
liu4 /bin/bash
liu5 /bin/bash
liu /bin/bash
例题:
# awk 'BEGIN{print "line one\nline two\nline three"}'
line one
line two
line three
# awk 'END{print "line one\nline two\nline three"}'line one
按Ctrl+D才显示最后三行。
line one
line two
line three
①awk里的变量的传递问题?
②shell里的变量传递到awk里的问题?
# cat -n passwd
例:显示文件的行数:
# cat -n passwd|awk 'BEGIN{i=0}{i++}END{print i}'
分析:每取一行,i++。最后i的值就正好是passwd文件的行数。
名称 | 用途 |
---|---|
NF | 每行$0的字段数 |
NR | 当前处理的行号 |
FS | 当前的分隔符,默认是空白字符 |
OFS | 当前的输出分隔符,默认是空白字符 |
例1:显示每行的字段数目
#awk '{print NF}' /etc/passwd
(默认分隔符是空白,所以字段数大都是1咯)
# awk -F: '{print NF}' /etc/passwd
(指定了分隔符了,就是7咯)
#awk '{print $1,$NF}' /etc/passwd
例3:显示每行的行号和内容
#awk -F: '{print NR,$0}' /etc/passwd
例4:显示第一列和第七列,中间用—隔开
#awk -F: 'BEGIN{OFS="---"}{print $1,$7}' /etc/passwd
简单写法:
#awk -F: 'OFS="---"{print $1,$7}'/etc/passwd
例5:显示符合模式的用户名和所在的行号最后显示总行号:
#awk 'BEGIN{FS=":"}/bash$/{print NR,$1}END{print NR}' /etc/passwd
例6:显示文件的3到5行(带行号,带内容)
#awk‘NR==3,NR==5{print NR,$0}’ /etc/passwd
#awk 'NR==4||NR==7{print NR,$0}' /etc/passwd
例7:显示文件的前10行(带行号,带内容):
#awk 'NR<=10{print NR,$0}' /etc/passwd
例8:显示文件的前10行和30到40行:
# awk 'NR<=10||NR>30&&NR<=40{print NR,$0}' /etc/passwd
练习:
1.分析下面三条命令的区别,为什么
①#awk ‘BEGIN{print NR}’ /etc/passwd (执行一次)
②#awk ‘{print NR}’ /etc/passwd (执行N次,N为行数)
(一直到48(最后一行))
③#awk ‘END{print NR}’ /etc/passwd (执行一次)
2.分析下面命令的执行结果
①#awk -F: ‘{print $NR}’/etc/passwd
(解析:第1行去第1个字段,第2行取第2个字段,第n行去第n个字段……)
②#awk -F: ‘{print NR, NF, 1, NF, $(NF-1)}’ /etc/passwd
(输出每行的行号,字段数,用户名,最后一个字段,倒数第二个字段)
3.只显示df -h结果的第一列文件系统
# df -h|awk -F' ' '{print $1}'
(注意指定分隔符为空白,默认就是空白)
4.显示passwd文件的第5行和第10行的行号和用户名
# cat passwd|awk -F: 'NR==5||NR==10{print NR,$1}'
1.使用NF变量显示passwd文件倒数第二列的内容
# cat passwd|awk -F: '{print $(NF-1)}'
2.显示passwd文件中第5到第10行的用户名
# cat passwd|awk -F: 'NR>=5&&NR<=10{print $1}'
3.显示passwd文件中第7列不是bash的用户名
# cat passwd|awk -F: '$7 ~/[^bash]$/{print $1}'
--》[^bash]中括号里的^表示取反。
或者:
# cat passwd|awk -F: '$7 !~/bash/{print $1}'
4.显示passwd文件中行号是5结尾的行号和行
# cat passwd|awk -F: 'NR%10==5{print NR,$0}'
# cat passwd|awk -F: 'NR ~/5$/{print NR,$0}'
5.用ifconfig只显示ip(不能使用tr或者cut命令)
# ifconfig|awk -F: '/inet addr/{print $2}'|awk '{print $1}'
192.168.1.147
127.0.0.1
# ifconfig |sed -n '/inet addr/p'|awk -F[:" "] '{print $13}'
6.使用awk显示eth0的入站流量和出站流量(字节)
# ifconfig eth0|awk -F'[: ]' '/RX bytes/{print $13,$19}'
# ifconfig eth0|tr -s ' '|awk -F'[: ]' '/RX bytes/{print $4,$9 }'
1025085 370703
# cat passwd|awk -F: 'BEGIN{print "查找结果";i=0}/^r/{print $1;i++}END{print i}'
查找结果
root
rpc
rpcuser
3
一、awk命令的引用shell变量
例1:
# name=haha
# echo|awk -v abc=$name '{print abc,$name}'
haha
# echo|awk -v abc=$name '{print $name}'
# echo|awk -v abc=$name '{print abc}'
haha --》引用awk变量无需加$
例2:
# name=haha;soft=xixi
# echo|awk -v abc=$name -v efg=$soft '{print abc,123}'
haha 123
分析:
#!/bin/bash
awk -v var=$1 -F: '$1==var{print NR,$0}' /etc/passwd
[第1个$1是位置变量,是执行此脚本时后面接的参数;第2个$1指的是用户名。]
awk编程语言内置了很多函数。
例1:利用length计算字符数目的函数来检查有无空口令用户。
#awk -F: 'length($2)==0{print $1}' /etc/passwd /etc/shadow
例2:显示文件中超过50个字符的行。
# awk 'length($0)>50{print NR,$0}' /etc/passwd
3 rpc:x:32:32:Rpcbind Daemon:/var/cache/rpcbind:/sbin/nologin
4 rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
7 mysql:x:27:27:MySQL Server:/var/lib/mysql:/bin/bash
# cat list
xixi 123
haha 456
hehe 789
# awk '{system("useradd "$1)}' list
# ls /home
haha hehe hello xixi
# awk '{system("userdel -r "$1)}' list
【命令用双括号括起,后面有空格】
# ls /home
hello
练习:利用system函数给上述用户配置密码。
#awk '{system("useradd $1");print $1 "is add"}' list
#awk '{system("userdel -r "$1);print $1 "is deleted"}' list
#awk '{system("echo "$2"|passwd --stdin "$1)}' list
【命令用双括号括起,后面有空格】
1.单分支
#awk -F: '{if($1 ~ /\<...\>/)print $0}' /etc/passwd
#awk -F: '{if($3 >= 500)print $1,$7}' /etc/passwd
2.双分支
#awk -F: '{if($3 != 0) print $1 ; else print $3}' /etc/passwd
3.多分支
#awk -F: '{if($1=="root") print $1;else if($1=="ftp") print $2;else if($1=="mail") print $3;else print NR}' /etc/passwd
例2:利用awk的if多重分支判断用户的类型,root显示为管理员 。
#awk -F: '{if($3==0) print $1,"管理员";else if($3>0 && $3<500) print $1,"系统用户";else if($3>=500 && $3 <= 60000) print $1,"普通用户";else print $1,"其它用户"}' /etc/passwd
例:利用awk的system命令在/tmp下建立/etc/passwd中与用户名同名的目录 。
#awk -F: '{system("mkdir /tmp/ "$1)}' /etc/passwd
练习1:用1条命令实现将指定目录下大于10K的对象复制到/tmp下(禁止使用find 和for) 。
#cp $(du -a /boot | awk '$1>10240{print $2}') /tmp
练习2:监控多台主机的磁盘分区一旦某台被监控主机的任一分区使用率大于80%, 就给root发邮件报警。
#!/bin/bash
warn=10
ip=(10.10.10.2 10.10.10.3)
for i in "${ip[@]}"
do
ssh root@$i df -Ph | tr -s " "|awk -v w=10 -F "[ %]" '/^\/dev/{if($5>w) print $1,$5"% useage is over 10%"}'>
alert
if [ -s alert ]
then
sed -i "1i $i" alert && mail -s "$i hd usage" root < alert
fi
done
练习3:检查/var/log/secure日志文件,如果有主机用root用户连接服务器的ssh服务失败次数超过10次(10次必须使用变量),就将这个IP地址加入/etc/hosts.deny文件拒绝其访问,如果这个IP已经存在就无需重复添加到/etc/hosts.deny文件(要求使用awk语句进行字符过滤、大小判断和变量赋值,禁止使用echo、sed、grep、cut、tr命令)
#/bin/bash
awk '/Failed password for root/{print $(NF-3)}' /var/log/secure|sort -nr| uniq -c > test --》sort 按降序排序,uniq -c统计次数
NUM=10
IP=$(awk '$1>num {print $2}' num=$NUM test) --》$1就是统计好的次数
for i in $IP
do
DENY=$(awk '$2==var {print $2}' var=$i /etc/hosts.deny)
if [[ -z $DENY ]]
then
awk '$2==var {print "sshd: "$2}' var=$i test >> /etc/hosts.deny
awk '$2==var {print "错误次数"$1,"拒绝"$2"访问"}' var=$i test
else
awk '$2==var {print "已经拒绝"$2"访问"}' var=$i test
fi
done
1.awk进行列求和
①统计/etc目录下以.conf结尾的文件的总大小
#find /etc/ -type f -name "*.conf" |xargs ls -l | awk '{sum+=$5} END{print sum}‘
②如果要匹配第一列的才累加,需要用到awk的数组和for循环(难点)
cat xferlog | awk '{print $7,$8}' | sort -n >/lianxi/123.txt
awk '{a[$1]+=$2}END{for(i in a) print i,a[i]}'/lianxi/123.txt | sort -rn -k2
解析:
a[$1]=a[$1]+$2--》a[172.16.1.3]=老的a[172.16.1.3]+对应次数
【xferlog是ftp服务器上的一个日志文件!】
【awk里的数组支持shell里讲的关联数组!!】
【sort -rn -k2是统计出下载量最大的ip并排序】
③awk进行行求和
例1:#echo 1 2 3 4 5 | awk ‘{for(i=1;i<=NF;i++) sum+=$i; print sum}’
例2:#seq -s ’ ’ 100 | awk ‘{for(i=1;i<=NF;i++) sum+=$i; print sum}’
PS:linux里记录行踪的地方
①history -c
②~/.bash_history
③/var/log/secure
④/var/log/lastlog
⑤/var/log/wtmp
练习:awk里如何使用数组来存放数据?
1.将所有的/etc/passwd所有的用户存放在user数组里。
# cat /etc/passwd|awk -F: '{user[$1]}'
解析:此时user[root]里面没有值,只是下标或者说索引变成了user[用户名]
# cat /etc/passwd|awk -F: '{user[$1]=$3}'
解析:此时,将$3的值(也就是uid)赋给user[$1]数组。
这样,就把用户和用户对应的uid关联起来了,用户名做下标关键字,uid做数组元素对应的值。
练习:awk里如何从数组里取出数据?
2.将user数组里的所有值取出来。
# cat /etc/passwd|awk -F: '{user[$1]=$3}END{for (i in user)print user[i],i}'
awk里关联数组难点的理解:
{a[$1]+=$2}
将$1对应的ip地址作为下标,将$2对应的大小赋值给下标为ip的元素。awk每读取一行就执行一次,重新又将$1对应的ip地址作为下标,执行a[$1]=a[$1]+$2,将上一行的a[$1]的值加上第2行$2值,实现累加的效果。但是ip地址还是那个ip地址,但是值已经累加了。
适用于:某一列不变,而对应的另一列的内容不同的场景。
练习:
username money
feng 100
feng 200
feng 360
li 100
li 150
zhang 90
zhang 88
统计每个人总共花了多少钱。并按总金额降序排序。
#cat money.txt|awk '{username[$1]+=$2}END{for(i in username)print i,username[i]}'|sort -nr -k2
练习2:/etc/passwd里的所有的用户的uid存放到一个数组,如果用户输入的用户名在数组里,就输出这个用户对应的uid。
用户名做下标;uid做元素值。
答案:
#!/bin/bash
read -p "Please input the username:" u_name
cat /etc/passwd|awk -v U_name=$u_name -F: '{user[$1]=$3}END{if (U_name in user)print user[U_name]}'
三种获得uid大于500小于10000的方法:
# cat /etc/passwd|awk -F: '$3>500&&$3<10000{print $1,$3}'
# cat /etc/passwd|awk -F: '($3>500&&$3<10000){print $1,$3}'
# cat /etc/passwd|awk -F: '{if($3>500&&$3<10000)print $1,$3}'