SHELL-正则表达式、SED与AWK编辑器
1.正则表达式
正则表达式是你所定义的模式模板(pattern template),Linux工具可以用它来过滤文本。在Linux中正则表达式会与通配符的使用产生混淆,这里可以通过其使用场景进行区分。处理文本内容使用的是正则表达式(文本三剑客 grep awk sed),通配符常用于过滤查看文件名(ls which whereis find)。
1.1常见正则表达式
正则表达式 | 含义 | 例 |
---|---|---|
\ | 转义字符,用于取消特殊符号的含义 | \!、\n、$ |
^ | 匹配字符串开始的位置 | a,the,^# |
$ | 匹配字符串结束的位置 | word$,^$ |
. | 匹配除\n以外的任意一个字符 | go.d,g..d |
* | 匹配前面子表达式0次或者多次 | good、go.d |
[list] | 匹配list列表中的1个字符 | |
[^list] | 匹配list列表中以外的字符 | [0-9][A-z0-9][^0-9] |
匹配前面的子表达式n次 | go{2}d,'[0-9]{2,}'匹配两位数字 | |
匹配前面子表达式n-m次 | ||
匹配前面子表达式不少于n次 | ||
\w | 匹配包括下划线的任意单词字符 | |
\W | 匹配任何非单词字符 | |
\d | 匹配一个数字字符 | |
\D | 匹配一个非数字字符 | |
\s | 空白符 | |
\S | 非空白符 |
扩展正则表达式:(egrep awk) grep -E sed -r
- +:匹配前面的子表达式1次以上,
- ?:匹配前面的子表达式0次或1次
- ():将括号中的字符串作为一个整体
- |:以或的方式匹配字符串。例:g(oo|la),匹配good或glad
1.2正则表达式例题
给定一个包含电话号码列表(一行一个电话号码)的文本文件 file.txt,写一个单行 bash 脚本输出所有有效的电话号码。你可以假设一个有效的电话号码必须满足以下两种格式: (xxx) xxx-xxxx 或 xxx-xxx-xxxx。(x 表示一个数字),你也可以假设每行前后没有多余的空格字符。
假设 file.txt 内容如下:
987-123-4567
123 456 7890
(123) 456-7890
你的脚本应当输出下列有效的电话号码:
987-123-4567
(123) 456-7890
答案:
grep -P '^(\([0-9]{3}\)\s|[0-9]{3}-)[0-9]{3}-[0-9]{4}$' file.txt
详解:
^:表示行首,以...开始,这里表示以(xxx) 或者xxx-开始,注意空格
():选择操作符,要么是([0-9]\{3\}) ,要么是[0-9]\{3\}-
|:或者连接操作符,表示或者
[]:单字符占位,[0-9]表示一位数字
{n}:匹配n位,[0-9]\{3\}匹配三位连续数字
$:表示行尾,结束
删除空行的方法:
grep -v '^$'
tr -s '\n'
sed '/^$/d'
2.SED编辑器
sed是一种流编辑器,流编辑器会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。
sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么储存在一个命令文本文件中。
sed的工作流程主要包括:
-
(1)读取: 一次从输入(文本、管道、标准输入)中读取一行数据。并储存到临时的缓存区中
-
(2)匹配: 根据所提供的编辑器命令匹配数据。
-
(3)修改:按照命令修改流中的数据。
-
(4)显示: 将新的数据输出到STDOUT。
2,3步骤可以统一为执行。
2.1 sed编辑器的使用
由于命令是按顺序逐行给出的,sed编辑器只需对数据流进行一遍处理就可以完成编辑操作。 这使得sed编辑器要比交互式编辑器快得多,可以快速完成对数据的自动修改。 sed命令的格式如:
sed options script file
sed的选项可以允许你修改sed命令的行为,常用选项如下:
- -e:用于指定命令处理输入的文本文件
- -f 用指定的脚本文件处理输入的文本文件
- -n 禁止sed编辑器输出
- -i:直接修改目标文本文件
sed默认情况下对文件内容的操作不会直接影响文件内容,除非重定向输出至源文件。或者使用了-i选项。
-e后可以追加的操作
- s:替换,替换指定字符
- d:删除,删除选定的行
- a:增加,在当前行下面增加一行指定内容
- i:插入,在选定行上面插入一行指定内容
- c:替换,将选定行替换为指定内容
- y:字符转换,转换前后的字符长度必须相同。
- l:打印数据流中的文本和不可见的ASCII字符
- p:打印字符
- =:打印行号
- q:退出命令
sed执行多条命令的方法:
sed -n '=;p' 可以通过分号隔离不同操作
或者
sed -n -e '=' -e 'p' 使用多个操作
2.1.1 sed的替换操作(s)
sed 替换模式
行范围(n,m)s/旧字符串+正则/新字符串/替换标记
s/old+正则/#& &代表正则表达式匹配到的内容
4种替换标记
- 数字:表明新字符串将替换第几处匹配的地方
- g:表明新字符串将会替换所有匹配的地方
- p:打印与替换命令匹配的行,一般与-n一起使用
- w 文件:将替换的结果写到文件中
[root@localhost data]# cat sedtrain.txt
1 aabbccddaa
2 aabbccddcc
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##将每行的第一个aa替换为admin
[root@localhost data]# sed -n 's/aa/admin/p' sedtrain.txt
1 adminbbccddaa
2 adminbbccddcc
5 adminbbccddaa
6 zzxxccadminxx
7 ffgghhiiadmin
##将每行的第2个aa替换为admin
[root@localhost data]# sed -n 's/aa/admin/2p' sedtrain.txt
1 aabbccddadmin
5 aabbccddadmin
##将每行的所有aa替换为admin
[root@localhost data]# sed -n 's/aa/admin/gp' sedtrain.txt
1 adminbbccddadmin
2 adminbbccddcc
5 adminbbccddadmin
6 zzxxccadminxx
7 ffgghhiiadmin
##将所有行所有位置的aa删除
[root@localhost data]# sed 's/aa//g' sedtrain.txt
1 bbccdd
2 bbccddcc
3 zzxxccvvff
4 qqwweerrdd
5 bbccdd
6 zzxxccxx
7 ffgghhii
##将1-3行的开头加入#号
[root@localhost data]# sed '1,3s/^/#/' sedtrain.txt
#1 aabbccddaa
#2 aabbccddcc
#3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##在以1开头的行尾加入#
[root@localhost data]# sed '/^1/s/$/#/' sedtrain.txt
1 aabbccddaa#
2 aabbccddcc
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##在以1或2开头的行尾加入#
[root@localhost data]# sed '/^[1,2]/s/$/#/' sedtrain.txt
1 aabbccddaa#
2 aabbccddcc#
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##在包含bbcc的行前加入#
[root@localhost data]# sed '/bbcc/s/^/#/' sedtrain.txt
#1 aabbccddaa
#2 aabbccddcc
3 zzxxccvvff
4 qqwweerrdd
#5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##在正则表达式匹配的内容后加入#,&表示正则表达式匹配的内容
[root@localhost data]# sed -n 's/.*bbcc/&#/p' sedtrain.txt
1 aabbcc#ddaa
2 aabbcc#ddcc
5 aabbcc#ddaa
##将第三行整体替换为ABC
[root@localhost data]# sed '3c ABC' sedtrain.txt
1 aabbccddaa
2 aabbccddcc
ABC
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##将包含bbcc的行替换为ABC
[root@localhost data]# sed '/bbcc/c ABC' sedtrain.txt
ABC
ABC
3 zzxxccvvff
4 qqwweerrdd
ABC
6 zzxxccaaxx
7 ffgghhiiaa
2.1.2 sed的插入行操作(a与i)
##在第一行前加入ABC
[root@localhost data]# sed '1i ABC' sedtrain.txt
ABC
1 aabbccddaa
2 aabbccddcc
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##在第二行后加入ABC
[root@localhost data]# sed '2a ABC' sedtrain.txt
1 aabbccddaa
2 aabbccddcc
ABC
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##在1-2行后加入ABC,共插入2行
[root@localhost data]# sed '1,2a ABC' sedtrain.txt
1 aabbccddaa
ABC
2 aabbccddcc
ABC
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
##在最后一行后加入ABC(原文本有空行)
[root@localhost data]# sed '$a ABC' sedtrain.txt
1 aabbccddaa
2 aabbccddcc
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
ABC
##在原文件最后读入sedtrainbak.txt内容
[root@localhost data]# sed '$r sedtrainbak.txt' sedtrain.txt
1 aabbccddaa
2 aabbccddcc
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
1 aabbccdd
2 aabbccdd
3 zzxxccvv
4 qqwweerr
5 aabbccdd
6 zzxxccaa
7 ffgghhii
2.1.3 sed的复制粘贴操作(H与G)
##1-3行,复制(H)到剪贴板,删除d 1-3行,粘贴G到最后一行$,相当于剪切操作
[root@localhost data]# sed '1,3{H;d};$G' sedtrain.txt
4 qqwweerrdd
5 aabbccddaa
6 zzxxccaaxx
7 ffgghhiiaa
1 aabbccddaa
2 aabbccddcc
3 zzxxccvvff
###1-3行,复制(H)到剪贴板,粘贴到第5行后
[root@localhost data]# sed '1,3H;5G' sedtrain.txt
1 aabbccddaa
2 aabbccddcc
3 zzxxccvvff
4 qqwweerrdd
5 aabbccddaa
1 aabbccddaa
2 aabbccddcc
3 zzxxccvvff
6 zzxxccaaxx
7 ffgghhiiaa
2.1.4 sed对字符位序变化操作
#将第一个字符与最后一个字符位置交换,这里使用了-r是为了支持扩展正则表达式
sed -r 's/^(.)(.*)(.)$'/\3\2\1/
[root@localhost data2]# echo 111222333 | sed -r 's/(111)(222)(333)/\3\2\1/'
333222111
3.awk
3.1 基本介绍
awk会逐行读取文件,默认分隔符为:空格与tab(制表符),将分割所得的各个字段保存在内建变量,按模式或者条件执行编辑命令。
与sed的区别,sed倾向于处理一整行数组。awk倾向于将一行分为多个字段然后在进行处理,因此可以变向完成对数据列的操作,awk的信息读入也是逐行读取的,执行结果可以通过print的功能将字段数据打印显示。在使用awk命令中,可以使用逻辑操作符“&&”、“||”、“!”;还可以进行简单的数学运算。
命令格式
awk 选项 '模式或条件' {操作}
文件1 文件2....
awk -f 脚本文件 文件1 文件2
....
awk常见的内建变量,可以直接使用:
- FS:列分隔符。指定每行文本的字段分隔符,默认空格或这表为。与“-F”作用相同
- NF:当前处理的行的字段个数
- NR:当前处理的行的行号(序数)
- $0:当前处理行的整体内容
- $n:当前处理的行的第n个字段(第n列)
- FILENAME:被处理的文件名
- RS:行分隔符。awk从文件上读取资料的时候,根据定义把资料分割为多条记录,而awk一次仅读入一条贾璐,以进行处理。预设值为'\n'(换行符)
3.2 按行输出文本
awk '{print}' file #输出file的所有内容
awk '{print $0}' file #输出file的所有内容
awk 'NR==1,NR==3{print}' file #输出第1~3行内容
awk '(NR>=1)&&(NR<=3){print}' file #输出第1~3行内容
awk '(NR%2)==1{print}' file #输出奇数行的内容
awk '(NR%2)==0{print}' fie #输出偶数行的内容
awk '/^root/{print}' /etc/passwd #输出以root开头的行
awk '/nologin$/{print}' /etc/passwd #输出以nologin结尾的行
awk 'BEGIN {x=0};/bash$/{x++};END {print x}' /etc/passwd #统计以/bin/bash 结尾的行,等同于 grep -c "/bin/bash" /etc/paswwd
BEGIN模式模式标识,在处理指定的文本之前,需要先执行BEGIN模式中指定的动作;awk再处理指定的文本,之后再执行END模式中指定的动作,END{}语句块中,往往会放入打印结果等语句。
在该条语句中,首先通过BEGIN定义了一个变量x,后通过/bash$/匹配以bash结尾的行,每匹配一次,x自加1,完成遍历后最后打印x的值。x++的方括号中可以添加其他命令,多条命令用分号隔离。
awk 'BEGIN {处理文本前的动作};{处理文本的动作};END {处理完文本后的动作}'
3.3 按字段输出文本
awk -F ":" '{print $3}' /etc/passwd #输出每行中(以空格或制表位分割)的第3个字段
awk -F ":" '{print $1,$3}' /etc/passwd #输出每行中的第1、3个子弹
awk 'BENGIN {FS=":"};{print $1,$3}' /etc/passwd #与上一条命令效果相同
awk -F ":" '$3<5{print $1,$3}' /etc/passwd #输出第3个字段的值小鱼5的第1、3个字段的内容
awk -F ":" '!($3<200){print}' /etc/passwd #输出第三个字段的值不小于200的行
awk -F 'BEGIN {FS=":"};{if($>=1000){print}}' /etc/passwd #在使用if,while等语句的时候需要加上花括号
awk -F ":" ':' '{max=($3>=$4)?$3:$4;{print max}}' /etc/passwd
#max=($3>=$4)?$3:$4;{print max};三元运算符,如果第三个字段的值大于等于第4个字段的值,则把第三个字段的值赋给max,否则第4个字段的值赋给max
(条件表达式)?(A表达式或值):(B表达式或值)
表达式成立为真的时候取:前的A的值
表达式不成立为假的时候取:后的B的值
awk -F ":" '{print NR,$0}' /etc/passwd #输出每行内容和行号,每处理完一条记录,NR值加1
awk -F ":" '$7~"/bash"{print $1}' /etc/passwd #输出以冒号为分隔且第7子弹中包含/bash的行的第一个字段
awk -F ":" '($1~"root")&&(NF==7){print $1,$2}' /etc/passwd #输出第一个字段中包含root且有7个字段的行的第1、2个字段。
$NF 代表最后一个字段
$n~"字符串" ~代表字段 包含 某个字符串的作用
$n=="字符串" ==代表字段 是 某个字符串的作用
$n!="字符串" !=代表字段 不为 某个字符串的作用
awk -F ":" '($7!="/bin/bash")&&($7!="/sbin/nologin"){print}' /etc/passwd
#输出第7个字段即不为/bin/bash,也不为/sbin/nologin的所有行
awk -F ":"
3.4 AWK处理文本实例
给定一个文件 file.txt,转置它的内容。
你可以假设每行列数相同,并且每个字段由 ' ' 分隔。
示例:
假设 file.txt 文件内容如下:
name age
alice 21
ryan 30
应当输出:
name alice ryan
age 21 30
答案:
awk '{ #这个大括号里的代码是 对正文的处理
# NF表示列数,NR表示已读的行数
# 注意for中的i从1开始,i前没有类型
for (i=1; i<=NF; i++){#对每一列
if(NR==1){ #如果是第一行
#将第i列的值存入res[i],$i表示第i列的值,i为数组的下标,以列序号为下标,
#数组不用定义可以直接使用
res[i]=$i;
}
else{
#不是第一行时,将该行对应i列的值拼接到res[i]
res[i]=res[i] " " $i
}
}
}
# BEGIN{} 文件进行扫描前要执行的操作;END{} 文件扫描结束后要执行的操作。
END{
#输出数组
for (i=1; i<=NF; i++){
print res[i]
}
}' file.txt