一、sed
-
编辑器
sed是一种流编辑器,流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流,sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储在一个命令文本文件中
-
工作流程
- 读取:sed 从输入流(文件、管道、标准输入)中读取一行内容并存储到临时的缓冲区中(又称模式空间,pattern space)
- 执行:默认情况下,所有的sed 命令都在模式空间中顺序地执行,除非指定了行的地址,否则sed 命令 将会在所有的行上依次执行
- 显示:发送修改后的内容到输出流。在发送数据后,模式空间将会被清空。在所有的文件内容都被处理完成之前,上述过程将重复执行,直至所有内容被处理完
- 注意:在所有的文件内容都被处理完成之前,上述过程将重复执行,直至所有内容被处理完;默认情况下所有的sed命令都是在模式空间内执行的,因此输入的文件并不会发生任何变化,除非是用重定向存储输出
-
命令格式
sed -e '操作' 文件1 文件2 ...
sed -n -e '操作' 文件1 文件2 ...
sed -f 脚本文件 文件1 文件2 ...
sed -i -e '操作' 文件1 文件2 ...
sed -e 'n{
操作1
操作2
...
}' 文件1 文件2 ...
-
常用选项
-e或 --expression= | 表示用指定命令来处理输入的文本文件,只有一个操作命令时可省略,一般在执行多个操作命令使用 |
-f 或 --file= | 表示用指定的脚本文件来处理输入的文本文件 |
-h 或 --help | 显示帮助 |
-n、--quiet 或silent | 禁止sed编辑器输出,但可以与p命令一起使用完成输出 |
-i | 直接修改目标文本文件 |
-
常用操作
s | 替换,替换指定字符 |
d | 删除,删除选定的行 |
a | 增加,在当前行下面增加一行指定内容 |
i | 插入,在选定行上面插入一行指定内容 |
c | 替换,将选定行替换为指定内容 |
y | 字符转换,转换前后的字符长度必须相同 |
p | 打印,如果同时指定行,表示打印指定行;如果不指定行,则表示打印所有内容;如果有非打印字符,则以 ASCII 码输出。其通常与“-n”选项一起使用 |
= | 打印行号 |
l(小写L) | 打印数据流中的文本和不可打印的ASCII字符(比如结束符$、制表符\t) |
-
打印内容
sed -n -e 'p' testfile1
sed -n -e '=' testfile1
sed -n -e 'l' testfile1
sed -n -e '=;p' testfile1
sed -n -e '=' -e 'p' testfile1
sed -n '
> =
> p
> ' testfile1
-
使用地址
以数字形式表示行区间
用文本模式来过滤出行
sed -n '1p' testfile1 #打印第一行
sed -n '$p' testfile1 #打印最后一行
sed -n '1,3p' testfile1 #打印1~3行
sed -n '3,$p' testfile1 #打印3~最后一行
sed -n '1,+3p' testfile1 #打印1之后的连续3行,即1-4行
sed '5q' testfile1 #打印前5行信息后退出,q表示退出
sed -n 'p;n' testfile1 #打印奇数行;n表示读取下一行,覆盖模式空间前一
sed -n 'n;p' testfile1 #打印偶数行
sed -n '2,${n;p}' testfile1 #从第二行开始,打印偶数行
sed -n '1~2p' testfile1 #跨两行读取
sed -n '1~3p' testfile1 #跨三行读取
sed -n '/user/p' /etc/passwd #打印含user的行
sed -n '/^a/p' /etc/passwd #打印a开头的行
sed -n '/bash$/p' /etc/passwd #打印以bash结尾的行
sed -n '2,/nobody/p' /etc/passwd
sed -n '2,/nobody/=' /etc/passwd
sed -nr '/ro{1,}t/p' /etc/passwd #-r表示支持扩展正则表达式
-
删除行
sed 'd' testfile1 #全删
sed '3d' testfile1 #删除第3行
sed '2,4d' testfile1 #删除第2~4行
sed '$d' testfile1 #删除最后一行
sed '/^$/d' testfile1 #删除空行
sed '/nologin$/d' /etc/passwd
sed '/nologin$/!d' /etc/passwd #“!”表示取反操作
sed '/2/,/3/d' testfile2 #从第一个位置打开行删除功能,到第二个位置关闭行删除功能
sed '/1/,/3/d' testfile2
-
替换
行范围 s/旧字符串/新字符串/替换标记
-
替换标记
数字 | 表明新字符串将替换第几处匹配的地方 |
g | 表明新字符串将会替换所有匹配的地方 |
p | 打印与替换命令匹配的行,与-n一起使用 |
w 文件 | 将替换的结果写到文件中 |
sed -n 's/root/admin/p' /etc/passwd
sed -n 's/root/admin/2p' /etc/passwd
sed -n 's/root/admin/gp' /etc/passwd
sed 's/root//g' /etc/passwd
sed '1,20 s/^/#/' /etc/passwd
sed '/^root/ s/$/#/' /etc/passwd
sed '/root/ s/^/#/' /etc/passwd
sed -rn 's/.*root.*/#&/p' /etc/passwd
sed -f script.sed testfile2
sed '1,20w out.txt' /etc/passwd
sed '1,20 s/^/#/w out.txt' /etc/passwd
sed -n 's/\/bin\/bash/\/bin\/csh/p' /etc/passwd
sed -n 's!/bin/bash!/bin/csh!p' /etc/passwd #使用“!”作为字符串分隔符
sed -i 's9\945\9\99\98\939g' test.txt #将94599替换为9893
-
插入
sed '/45/c ABC' testfile2
sed 'y/145/ABC/' testfile2 #使所有的1字符转换成A,所有的2字符转换成B,所有的3字符转换成C
sed '1,3a ABC' testfile2
sed '/root/{H;d};$G' /etc/passwd #将包含root的行剪切到末尾,H表示复制到剪切板,G表示粘贴到指定行后
sed '1,2H;3,4G' /etc/passwd #将1、2行复制到3和4行的下面
echo "111222333" | sed -r 's/(111)(222)/\2\1/' #将字符 111 和 222 互换位置
echo "111222333" | sed -r 's/^(.)(.*)(.)$/\3\2\1/' #将第一个字符和最后一个字符互换
sed "$!N;s/\n/ /" testfile2 #N表示追加下一行到模式空间,同时将两行看做一行,两行之间含有\n换行符。$!N表示最后一行不执行N
二、awk
-
工作原理
逐行读取文本,默认以空格或tab键为分隔符进行分隔,将分隔所得的各个字段保存到内建变量中,并按模式或者条件执行编辑命令
sed命令常用于一整行的处理,而awk比较倾向于将一行分成多个“字段”然后再进行处理。awk信息的读入也是逐行读取的,执行结果可以通过print的功能将字段数据打印显示。在使用awk命令的过程中,可以使用逻辑操作符“&&”表示“与”、“||”表示“或”、“!”表示“非”;还可以进行简单的数学运算,如+、-、*、/、%、^分别表示加、减、乘、除、取余和乘方
awk 选项 '模式或条件 {操作}' 文件 1 文件 2 …
awk -f 脚本文件 文件 1 文件 2 …
-
常见的内建变量
FS | 列分割符。指定每行文本的字段分隔符,默认为空格或制表位 与"-F"作用相同 |
NF | 当前处理的行的字段个数 |
NR | 当前处理的行的行号(序数) |
$0 | 当前处理的行的整行内容 |
$n | 当前处理行的第n个字段(第n列) |
FILENAME | 被处理的文件名 |
RS | 行分隔符 awk从文件上读取资料时,将根据RS的定义把资料切割成许多条记录,而awk一次仅读入一条记录,以进行处理。预设值是'\n' |
-
按行输出文本
#输出所有内容
awk '{print}' 文件
awk '{print $0}' 文件
awk 'NR==n,NR==m{print}' 文件 #输出第n~m行内容
awk '(NR>=n)&&(NR<=m){print}' 文件 #输出第n~m行内容
awk 'NR==n||NR==m{print}' 文件 #输出第n行、第m行内容
awk '(NR%2)==1{print}' 文件 #输出所有奇数行的内容
awk '(NR%2)==0{print}' 文件 #输出所有偶数行的内容
awk '/^.../{print}' 文件 #输出以...开头的行
awk '/...$/{print}' 文件 #输出以...结尾的行
awk 'BEGIN {x=0};/\/bin\/bash$/{x++};END {print x}' /etc/passwd #统计以/bin/bash 结尾的行数,等同于 grep -c "/bin/bash$" /etc/passwd
#BEGIN模式表示,在处理指定的文本之前,需要先执行BEGIN模式中指定的动作;awk再处理指定的文本,之后再执行END模式中指定的动作,END{}语句块中,往往会放入打印结果等语句
-
按字段输出文本
awk -F ":" '{print $n}' 文件 #输出每行中(以空格或制表位分隔)的第n个字段
awk -F ":" '{print $n,$m}' 文件 #输出每行中的第n、m个字段
awk -F ":" '$n<m{print $l,$k}' 文件 #输出第n个字段的值小于m的第l、k个字段内容
awk -F ":" '!($n<200){print}' 文件 #输出第n个字段的值不小于200的行
awk 'BEGIN {FS=":"};{if($3>=1000){print}}' /etc/passwd #先处理完BEGIN的内容,再打印文本里面的内容
awk -F ":" '{max=($n>=$m)?$n:$m;{print max}}' 文件 #($n>$m)?$n:$m;三元运算符,如果第n个字段的值大于等于第m个字段的值,则把第n个字段的值赋给max,否则第m个字段的值赋给max
awk -F ":" '{print NR,$0}' 文件 #输出每行内容和行号,每处理完一条记录,NR值加1
awk -F ":" '$n~"/bash"{print $m}' 文件 #输出以冒号分隔且第n个字段中包含/bash的行的第m个字段
awk -F ":" '($n~"...")&&(NF==m){print $l,$k}' 文件名 #输出第n个字段中包含...且有m个字段的行的第l、k个字段
awk -F ":" '($7!="/bin/bash")&&($7!="/sbin/nologin"){print}' /etc/passwd #输出第7个字段既不为/bin/bash,也不为/sbin/nologin的所有行
-
通过管道、双引号调用 Shell 命令
echo $PATH | awk 'BEGIN{RS=":"};END{print NR}' #统计以冒号分隔的文本段落数,END{}语句块中,往往会放入打印结果等语句
awk -F: '/bash$/{print | "wc -l"}' /etc/passwd #调用 wc -l 命令统计使用 bash 的用户个数,等同于 grep -c "bash$" /etc/passwd
free -m | awk '/Mem:/ {print int($3/($3+$4)*100)"%"}' #查看当前内存使用百分比
top -b -n 1 | grep Cpu | awk -F ',' '{print $4}' | awk '{print $1}' #查看当前CPU空闲率,(-b -n 1 表示只需要1次的输出结果)
date -d "$(awk -F "." '{print $1}' /proc/uptime) second ago" +"%F %H:%M:%S" #显示上次系统重启时间,等同于uptime;second ago为显示多少秒前的时间,+"%F %H:%M:%S"等同于+"%Y-%m-%d %H:%M:%S"的时间格式
/proc/uptime 第一列输出的是,系统启动到现在的时间(以秒为单位);第二列输出的是,系统空闲的时间(以秒为单位)
date -d "$(date -d"1 month" +"%Y%m01") -3 day" +"%Y%m%d" 当月倒数第三天
date +"%Y%m01" 当月第一天
awk 'BEGIN {n=0 ; while ("w" | getline) n++ ; {print n-2}}' #调用w命令,并用来统计在线用户数
awk 'BEGIN {"hostname" | getline ; {print $0}}' #调用 hostname,并输出当前的主机名
seq 10 | awk '{getline; print $0}' #获取偶数行
seq 10 | awk '{print $0; getline}' #获取基数行
当getline左右无重定向符“<”或“|”时,awk首先读取到了第一行,就是1,然后getline,就得到了1下面的第二行,就是2,因为getline之后,awk会改变对应的NF,NR,FNR和$0等内部变量,所以此时的$0的值就不再是1,而是2了,然后将它打印出来。
当getline左右有重定向符“<”或“|”时,getline则作用于定向输入文件,由于该文件是刚打开,并没有被awk读入一行,只是getline读入,那么getline返回的是该文件的第一行,而不是隔行。
FNR:awk当前读取的记录数,其变量值小于等于NR(比如当读取第二个文件时,FNR是从0开始重新计数,而NR不会)。
NR==FNR:用于在读取两个或两个以上的文件时,判断是不是在读取第一个文件
-
实用案例
date -d "$(awk -F. '{print $1}' /proc/uptime) second ago" +"%Y%m%d %H:%M:%S" #获取本机上一次开机时间
uptime | awk '{print $NF}' #检测本机CPU最近15分钟的负载
top -b -n 1 | grep Cpu | awk -F ',' '{print $4}' | awk '{print $1}' #查看CPU空闲率
free -m | awk '/Mem:/ {print int($3/($3+$4)*100)"%"}' #内存使用率
df | grep -w "/" | awk '{print $5}' | awk -F% '{print $1"%"}' #磁盘分区容量使用率
#检测入站网卡流量和出站网卡的流量
ifconfig ens33 | awk '/RX p/{print $5}'
ifconfig ens33 | awk '/TX p/{print $5}'
#使用awk 统计 httpd 访问日志中每个客户端IP的出现次数
awk '{ip[$1]++}END{for(i in ip){print ip[i],i}' /var/log/httpd/access_log | sort -r
备注:定义数组,数组名称为ip,数字的下标为日志文件的第1列(也就是客户端的IР地址),++的目的在于对客户端进行统计计数,客户端IP出现一次计数器就加1。END中的指令在读取完文件后执行,通过循环将所有统计信息输出,for循环遍历的是数组名ip的下标。
#使用 awk 去重
arr=(1 2 3 4 5 5 4 3 2 1)
awk -v RS=' ' '!a[$1]++' <<< ${arr[@]}
#-v 设置变量
#<<< 表示将后面的内容作为前面命令的标准输入
#awk '1' 就是 awk '1{print}' ,允许打印读入的行内容,例:echo 123 | awk '1'
#awk '0' 就是 awk '0{print}' ,不允许打印读入的行内容,例:echo 123 | awk '0'
#var++ 的形式:先读取 var 变量值,再对 var 值 +1
awk 处理第一行时:先读取 a[$1] 值再自增,a[$1] 即 a[1] 值为空(即0),即为 awk '!0',即为 awk '1',即为 awk'1{print}'
awk 处理第二行时:先读取 a[$1] 值再自增,a[$1] 即 a[1] 值为 1,即为 awk '!1',即为 awk '0',即为 awk '0{print}'