sed和awk的详细用法

sed用法

sed(Stream EDitor)是一款流编辑器,用来对文本进行过滤与替换操作。其原理是:通过文件或管道读取文件内容,但是sed默认并不直接修改源文件,而是一次仅读取文件的一行至模式空间(pattern space)根据sed指令进行编辑并输出结果后清除模式空间,即所有的操作都是在模式空间中进行的。

语法格式

sed [option]… ‘script’ inputfile…

OPTIONS选项

-n,–quite,–silent:静默输出,即不输出模式中的内容至输出

-e script:使用多个脚本指令执行编辑

-f /PATH/TO/SCRIPT_FILE:从指定的文件中读取编辑脚本

-r:支持在脚本中使用正则表达式

-i,–in-place:直接在文件原处执行编辑,即修改源文件

SCRIPT,地址定界+编辑指令

地址定界

(1) 不给地址:对全文进行处理;

(2) 单地址

1
2
3
4
5
#:指定的行

$:文件的最后一行

/PATTERN/:被模式匹配到的每一行

(3) 地址范围

1
2
3
4
5
6
7
m,n:指定m行到n行

#,+n:指定#行到#+n行

/pat1/,/pat2/:匹配到的pat1到pat2之间的行

#,/pat/:第#行到匹配到pat

(4) 步进:~

1
2
3
1~2:所有奇数行,从一开始,步进2

2~2:所有偶数行,从二开始,步进2

注意:如果//中正则表达式为空,则匹配最近一次正则表达式的匹配地址!

编辑命令

d:删除

p:显示模式空间的内容

a \text:在行后面追加内容,支持使用\n(换行符)实现多行追加

i \text:在行前面插入内容,支持使用\n(换行符)实现多行追加

c \text:替换行为单行或多行文本,支持使用\n(换行符)

w /PATH/TO/SOMEFILE:保存模式空间匹配到的行至指定文件中

r /PATH/FROM/SOMEFILE:读取指定文件的文本流至模式空间中匹配到的行的行后

=:为模式空间中的行打印行号

!:取反条件

s/pattern/replacement/flags:替换,支持使用其他分隔符,如:s@@@, s###

1
2
3
4
5
6
7
8
替换标记(flags):
#:替换行内匹配到的第#次的内容
g:行内全局替换
p:显示替换成功的行
w /PATH/TO/SOMEFILE:将替换后的结果保存至指定文件
replacement:
&:用pattern匹配到的内容进行替换
\n:在pattern中使用\(\)指定时,匹配第n个子串

【难点】高级编辑命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
h:把模式空间中的内容覆盖至保持空间中;
H:把模式空间中的内容追加至保持空间中;
g:把保持空间中的内容覆盖至模式空间中;
G:把保持空间中的内容追加至模式空间中;
x:把模式空间中的内容与保持空间中的内容互换;
n:显示并清空模式空间,然后读取匹配到的行的下一行覆盖至模式空间;
N:追加读取匹配到的行的下一行至模式空间中;
d:删除模式空间中的行;
D:删除多行模式空间中的所有行;

示例:
sed -n 'n;p' FILE:显示偶数行;
sed '1!G;h;$!d' FILE:逆序显示文件的内容;
sed ’$!d' FILE:取出最后一行;
sed '$!N;$!D' FILE:取出文件后两行;
sed '/^$/d;G' FILE:删除原有的所有空白行,而后为所有的非空白行后添加一个空白行;
sed 'n;d' FILE:显示奇数行;
sed 'G' FILE:在原有的每行后方添加一个空白行;

awk用法

其用法博大精深,且听我细细道来

首先,grep,sed,awk三者区别,回顾一下:

grep, egrep, fgrep

文本过滤工具,只读不写,加上各种正则及关键字,那叫一个爽快。

sed

行编辑器,有模式空间及保持空间,实现文本行的编辑及输出。

awk

报告生成器,格式化文本输出;功能及其强大,不光可用正则,还可以使用条件判断语句,函数、数组,不愧为上古神器。

有一点很重要,也很激励人心,就是“结合awk与其他工具诸如grep和sed,将会使shell编程更加容易,也更有逼格。”

awk命令的使用格式:

awk [options] ‘program’ file1,file2,…

awk [options] ‘PATTERN { action }’ file1,file2,…

program与PATTERN { action }的关系

很多同学在刚开始学习awk的时候,这里常常犯迷糊,me too!所以我觉得这个关系先理清楚,对awk往后的学习是有帮助的。

program包括PATTERN和ACTION,PATTERN和ACTION最少得有一个,语句之间用分号分隔

【理解关键点】模式( pattern ) 用于匹配输入中的每行文本。对于匹配上的每行文本,awk 都执行对应的 动作( action )。模式和动作之间使用花括号隔开。awk 顺序扫描每一行文本,并使用 记录分隔符(一般是换行符)将读到的每一行作为 记录,使用 域分隔符( 一般是空格符或制表符 ) 将一行文本分割为多个 域, 每个域分别可以使用 $1, $2, … $n 表示。$1 表示第一个域,$2 表示第二个域,$n 表示第 n 个域。 $0 表示整个记录。模式或动作都可以不指定,缺省模式的情况下,将匹配所有行。缺省动作的情况下,将执行动作 {print},即打印整个记录。

先来说说ACTION

其中最常用的ACTION参数之一就是print和printf了,前者实现打印输出,后者实现格式化输出

print

语法:

print item1, item2, …

要点

(1) 逗号分隔符;

(2) 输出的各item可以字符串,也可以是数值;当前记录的字段、变量或awk的表达式;

(3) 如省略item,相当于print $0,打印整行字符

printf命令,这个就很有内涵了

要点:

(1) FORMAT必须给出;

(2) 不会自动换行,需要显式给出换行控制符,\n

(3) FORMAT中需要分别为后面的每个item指定一个格式化符号;

语法:

printf 格式符/修饰符, item1, item2, …

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
格式符:
%c: 显示字符的ASCII码;
%d, %i: 显示十进制整数;
%e, %E: 科学计数法数值显示;
%f:显示为浮点数;
%g, %G:以科学计数法或浮点形式显示数值;
%s:显示字符串;
%u:无符号整数;
%%: 显示%自身;

修饰符:
#[.#]:第一个数字控制显示的宽度;第二个#表示小数点后的精度;
%3.1f
-: 左对齐
+:显示数值的符号

记住哦,格式符和修饰符可以灵活组合使用

ACTION还支持以下干货

(1) Expressions表达式

(2) Control statements:if, while等;

(3) Compound statements:组合语句;

(4) input statements

(5) output statements(print printf)

我们来看看控制语句:

(1)if(condition) {statments} :单分支if语句

(2)if(condition) {statments} else {statements} :双分支if语句

(3)while(conditon) {statments}

(4)do {statements} while(condition) :循环体

(5)for(expr1;expr2;expr3) {statements}

(6)break

(7)continue

(8)delete array[index]

(9)delete array

(10)exit

支持这么多,已经看傻,下面一个个来看看:

if-else

1
2
3
4
5
6
7
8
9
10
11
语法:if(condition) statement [else statement]

~]# awk -F: '{if($3>=1000) {printf "Common user: %s\n",$1} else {printf "root or Sysuser: %s\n",$1}}' /etc/passwd

~]# awk -F: '{if($NF=="/bin/bash") print $1}' /etc/passwd

~]# awk '{if(NF>5) print $0}' /etc/fstab

~]# df -h | awk -F[%] '/^\/dev/{print $1}' | awk '{if($NF>=20) print $1}'

使用场景:对awk取得的整行或某个字段做条件判断;

while循环

1
2
3
4
5
6
7
8
语法:while(condition) statement
条件“真”,进入循环;条件“假”,退出循环;

~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg

~]# awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {if(length($i)>=7) {print $i,length($i)}; i++}}' /etc/grub2.cfg

使用场景:对一行内的多个字段逐一类似处理时使用;对数组中的各元素逐一处理时使用;

do-while循环

1
2
语法:do statement while(condition)
意义:至少执行一次循环体

for循环

1
2
3
4
5
6
7
8
9
语法:for(expr1;expr2;expr3) statement

for(variable assignment;condition;iteration process) {for-body}

~]# awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg

特殊用法:
能够遍历数组中的元素;
语法:for(var in array) {for-body}

switch语句

1
语法:switch(expression) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; ...; default: statement}

next

1
2
3
提前结束对本行的处理而直接进入下一行;

~]# awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd

数组array

1
2
3
4
5
6
7
8
9
10
11
   关联数组:array[index-expression]

index-expression:
(1) 可使用任意字符串;字符串要使用双引号;
(2) 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为“空串”;

若要判断数组中是否存在某元素,要使用"index in array"格式进行;
weekdays[mon]="Monday"

若要遍历数组中的每个元素,要使用for循环;
for(var in array) {for-body}

函数

1
2
3
4
5
6
7
8
9
内置函数
数值处理:
rand():返回0和1之间一个随机数;

字符串处理:
length([s]):返回指定字符串的长度;
sub(r,s,[t]):以r表示的模式来查找t所表示的字符中的匹配的内容,并将其第一次出现替换为s所表示的内容;
gsub(r,s,[t]):以r表示的模式来查找t所表示的字符中的匹配的内容,并将其所有出现均替换为s所表示的内容;
split(s,a[,r]):以r为分隔符切割字符s,并将切割后的结果保存至a所表示的数组中;

好,上边ACTION啰嗦完了,哈哈哈哈哈,下面就要高潮咧O(∩_∩)O哈哈~

PATTERN

PATTERN有什么,有很多,超乎你的想象,它支持

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(1) empty:空模式,匹配每一行;

(2) /regular expression/:仅处理能够被此处的模式匹配到的行;

(3) relational expression: 关系表达式;【结果有“真”有“假”;结果为“真”才会被处理】;

【真:结果为非0值,非空字符串】这个跟bash的命令返回值不一样;

(4) line ranges:行范围,
startline,endline:/pat1/,/pat2/

例子:[root@CentOS7 ~]# awk -F: '/^h/,/^u/{print $1}' /etc/passwd

注意: 不支持直接给出数字的格式

可以使用如下方式:
~]# awk -F: '(NR>=2&&NR<=10){print $1}' /etc/passwd

(5) BEGIN/END模式
BEGIN{}: 仅在开始处理文件中的文本之前执行一次;
END{}:仅在文本处理完成之后执行一次 ;

选项[options]


-F:指明输入时用到的字段分隔符;

-v var=value: 自定义变量;

awk的options主要用来定义各种变量和使用各种內建变量。

内建变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FS:input field seperator,默认为空白字符;
OFS:output field seperator,默认为空白字符;
RS:input record seperator,输入时的换行符;
ORS:output record seperator,输出时的换行符;

NF:number of field,字段数量
{print NF}, {print $NF} 它们之间是有区别的,NF是有多少个字段数量,而$NF表示最后一个字段的内容
NR:number of record, 行数;
FNR:各文件分别计数;行数;

$0:代表一行,读一行进行切片

FILENAME:当前文件名;

ARGC:命令行参数的个数;
ARGV:数组,保存的是命令行所给定的各参数;

PS:在awk中要做变量替换,是不能用引号引起来的

自定义变量

1
2
3
(1) -v var=value  #变量名区分字符大小写哦;

(2) 在program中直接定义

好了,awk就介绍到这里,下面来写例子

例子1:显示/etc/passwd的账户

1
2
3
4
5
#cat /etc/passwd |awk  -F ':'  '{print $1}'  
root
daemon
bin
sys

上边这种是awk+action的示例,每行都会执行action{print $1}。

-F指定域分隔符为’:’。

例子2:搜索/etc/passwd有root关键字的所有行

1
2
awk -F: '/root/' /etc/passwd
root:x:0:0:root:/root:/bin/bash

上边这种是pattern的使用示例,匹配了pattern(这里是root)的行才会执行action(没有指定action,默认输出每行的内容)。

搜索支持正则,例如找root开头的: awk -F: ‘/^root/‘ /etc/passwd

例子3:搜索/etc/passwd有root关键字的所有行,并显示对应的shell

1
2
awk -F: '/root/{print $7}' /etc/passwd             
/bin/bash

上边这里指定了匹配了pattern(这里是root)的行才会执行action{print $7}

例子4:删除/boot/grub/grub.conf文件中所有行的行首的空白字符

sed ‘s/^[[:space:]]+//‘ /boot/grub/grub.conf

这里的sed使用了查找替换,思路就是先查找文件中所有行的行首的空白字符,用正则的行首锚定空字符,之后查找替换为空,即删除。

例子5:删除/etc/fstab文件中所有以#开头,后跟至少一个空白字符的行的行首的#和空白字符

sed ‘s/^#[[:space:]]*//‘ /etc/fstab

同样是查找替换的套路,参照例子4

例子6:把/etc/fstab文件的奇数行另存为/tmp/fstab.3

sed ‘1~2!d’ /etc/fstab > /tmp/fstab.3

sed ‘1~2w /tmp/fstab.3’ /etc/fstab

这里用到了sed地址定界中的步进+编辑命令,两种不同的编辑命令实现,真是飞刀又见飞刀,套路又见套路,套路就是熟悉其语法,命令,灵活组合使用。

例子7:echo一个文件路径给sed命令,取出其基名;进一步地,取出其路径名

路径名

echo “/tmp/test/fstab” | sed ‘s#[^/]+\?$##’

上边sed的#为分隔符,[^/]指匹配指定范围外的任意单个字符,

+:匹配其前面的字符1次或多次:即其前面的字符要出现至少1次;

\?:匹配其前面的字符0次或1次;即其前面的字符是可有可无的;

$:行尾锚定;用于模式的最右侧

基名

echo “/tmp/test/fstab” | sed ‘s#(\/.*\/)##’

1
2
3
4
5
上边的sed分隔符为#,用到了sed的查找替换、正则表达式及其分组,形式为\(正则表达式\)

正则为\/.*\/,其中\为转移符,正则抽取出来就是/.*/ 意思就是匹配到/开头的以/结尾期间的任意单个字符任意次。

各位应该明白了吧。

例子8:统计指定文件中所有行中每个单词出现的次数

awk ‘{for(i=1;i<=NF;i++){count[$i]++}}END{for(i in count) {print i,count[i]}}’ /etc/fstab

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@CentOS7 ~]# awk '{for(i=1;i<=NF;i++){count[$i]++}}END{for(i in count) {print i,count[i]}}' /etc/fstab
swap 2
fstab(5), 1
filesystems, 1
on 1
/etc/fstab 1
/boot 1
more 1
mount(8) 1
pages 1
'/dev/disk' 1
/dev/mapper/cl_centos7-swap 1
blkid(8) 1
See 1
/dev/mapper/cl_centos7-root 1
for 1
and/or 1
anaconda 1
/ 1
findfs(8), 1
under 1
17:33:09 1
Created 1
UUID=fe2021fb-ac21-474d-8256-f72b87fc915a 1
0 6
info 1
Accessible 1
22 1
# 7
defaults 3
xfs 2
man 1
are 1
reference, 1
Mar 1
by 2
maintained 1
2017 1
Wed 1
[root@CentOS7 ~]#

7、统计当前系统上所有tcp连接的各种状态的个数;

netstat -n | awk ‘/^tcp/ {++state[$NF]} END {for(key in state) print key,”t”,state[key]}’

例子

1
2
3
4
5
6
7
root@xxx:~# netstat -n | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"t",state[key]}'
FIN_WAIT2 t 71
CLOSE_WAIT t 1
TIME_WAIT t 2049
ESTABLISHED t 1392
LAST_ACK t 3
FIN_WAIT1 t 4

8、统计指定的web访问日志中各ip的资源访问次数:

awk ‘{ip[$1]++}END{for(i in ip) print i,ip[i]}’ /opt/nginx/logs/access.log

9、写一个脚本:定义一个数组,数组元素为/var/log目录下所有以.log结尾的文件的名字;显示每个文件的行数;

1
2
3
4
5
6
7
8
9
[root@CentOS7 ~]#  cat week15_title9.sh 
#!/bin/bash

files=/var/log/*.log

for i in $files
do
wc -l $i
done

输出如下:

1
2
3
4
5
6
7
8
9
10
11
[root@CentOS7 ~]# ./week15_title9.sh 
248 /var/log/boot.log
9 /var/log/openlmi-install.log
177 /var/log/vmware-install.log
326 /var/log/vmware-vmsvc.log
85 /var/log/vmware-vmusr.log
10 /var/log/wpa_supplicant.log
436 /var/log/Xorg.0.log
320 /var/log/Xorg.9.log
328 /var/log/yum.log
[root@CentOS7 ~]#

10、写一个脚本,能从所有同学中随机挑选一个同学回答问题;进一步地:可接受一个参数,做为要挑选的同学的个数;

1
2
3
4
5
6
7
8
9
10
11
12
[root@CentOS7 ~]# cat week15_title10.sh
#!/bin/bash

if [ $1 ];then #判断有无脚本参数传入,如有则执行,没有就执行else的语句
for((i=1;i<=$1;i++));do
a=$[$RANDOM%11] #定义一个0~10的随机数
echo $a
done
else
a=$[$RANDOM%11]
echo $a
fi

11、授权centos用户可以运行fdisk命令完成磁盘管理,以及使用mkfs或mke2fs实现文件系统管理;

1
2
visudo
centos ALL=(root) NOPASSWD:/sbin/fdisk,/sbin/mke2fs,/sbin/mkfs

12、授权gentoo用户可以运行逻辑卷管理的相关命令;

1
2
visudo
gentoo ALL=(root) lvm

13、基于pam_time.so模块,限制用户通过sshd服务远程登录只能在工作时间进行;

1
2
vim /etc/ssh/sshd_config 
UsePAM yes #开启Pam模块认证

1
2
3
4
5
6
7
/lib64/security/pam_time.so #确保pam_time.so存在 

vim /etc/pam.d/sshd
增加 account required pam_time.so

vim /etc/security/time.conf
添加 *;*;*;MoTuWeThFr0900-1800

14、基于pam_listfile.so模块,定义仅某些用户,或某些组内的用户可登录系统;

1
2
3
4
5
6
7
8
9
[root@localhost ~]# vim /etc/sshd_userlist
root
centos
gentoo

chmod 600 /etc/sshd_userlist
chown root /etc/sshd_userlist
vim /etc/pam.d/sshd
#auth required pam_listfile.so item=user sense=allow file=/etc/sshd_userlist onerr=succeed
0%