Fork me on GitHub

shell脚本5-sed的使用

sed 的工作模式

  • sed(Stream Editor),流编辑器。对标准输出或文件逐行输出。

语法格式:

  • stdout | sed [option] "pattern command"
  • sed [option] "pattern command" file

逐行根据命令来进行匹配。

sed 的选项

sed的选项控制的是sed的处理流程。

选项 含义
-n 只打印模式匹配行
-e 直接在命令行进行sed选项,默认选项
-f 后面接一个文件名,将命令保存在文件里面,指定文件去执行即可
-r 支持拓展正则表达式
-i 直接修改文件内容

新建一个file文件,里面写入:

1
2
3
I love javascript
I LOVE JAVASCRIPT
Hadoop is big date frame

然后使用sed 'p' file去对文件里面的每一行内容去进行一个打印:

1
2
3
4
5
6
I love javascript
I love javascript
I LOVE JAVASCRIPT
I LOVE JAVASCRIPT
Hadoop is big date frame
Hadoop is big date frame

这种情况下sed执行之后会对每一行进行两次输出。

如果我们使用一个选项之后就可以对每行只输出一次:
sed -n 'p' file

sed '/javascript/p' file使用这个命令可以去处理一波每一行的里面带有javascript字符的行并对这一行去进行一个输出。

这个地方打印的结果会是:

1
2
3
4
I love javascript
I love javascript
I LOVE JAVASCRIPT
Hadoop is big date frame

这里很明显到了没有被sed处理到的行会被打印出来(在默认没有对sed添加选项的情况下)。被处理的行会被打印两次,如果我们需要对这种情况进行一个处理,可以添加一个选项-n来进行操作就行了。

sed -n '/javascript/p' file

这个时候就只会打印出一行:

1
I love javascript

当我们想写多种匹配模式的时候,可以使用这样的命令去写:

sed -n -e '/javascript/p' -e '/JAVASCRIPT/p' file

这种情况下我们就可以同时去使用两种匹配模式。

1
2
I love javascript
I LOVE JAVASCRIPT

我们可以编辑一个文件vim edit.sed

内容如下:

1
/javascript/p

然后使用sed -n -f edit.sed file去对file文件进行一个查找,这样还是可以输出:

1
I love javascript

sed -n '/javascript|JAVASCRIPT/p' -r file

这种情况下是可以支持一种拓展正则表达式的使用的(这样就可以输出两行字符串):

1
2
I love javascript
I LOVE JAVASCRIPT

sed -n -r 's/(love|LOVE)/like/g;p' file

s后面跟///是一个固定的写法,这里表示会把love和LOVE替换成like.

然后p表示将结果进行一个输出,但是这种情况修改之后并不会对我们的原来的文件造成任何的影响,于是这个时候我们需要去使用另外一个选项去对里面的字符串去进行一个真正的修改:

sed -i s/(love|LOVE)/like/g file

这个时候再次打开file就会发现之前的内容已经被修改了。

这样我们常见的sed选项已经使用完成了。在正式的生产环境里面使用最多的是其实还是-i命令去进行一个文本的替换。

sed中的pattern详解

pattern的所有用法表:

匹配模式 含义
10command 匹配到第10行
10,20command 匹配到第10行开始,到第20行结束
10,+5command 匹配到第10行开始,到第16行结束
/pattern1/command 匹配到pattern1的行
/pattern1/,/pattern2/command 匹配到pattern1的行开始,到匹配到pattern2的行结束
10,/pattern1/command 匹配到从第10行开始,到匹配到pattern1的行结束
pattern1/,10command 匹配到pattern1的行开始,到第10行匹配结束

几乎已经包含了所有的匹配模式,这些情况里面使用的最多的是第四种和第五种模式。

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
# 1.直接指定一个行号
# ex:打印file文件的第17行
sed -n "17p" file
# 2.指定起始行号和结束行号
# ex:打印文件的第10到20行
sed -n "10,20p" file
# 3.指定起始行号,然后后面N行
# ex:打印file文件从第10行开始,在后面第5行的所有内容
sed -n "10,+5p" file
# 4.正则表达式匹配的行
# 这种模式是使用非常多的一种模式()
# ex:打印file文件中以root开头的行
sed -n "/^root/p" file
sed -n '/bash/p' /etc/passwd
# 匹配该文件下面的`/sbin/nologin`字符串(注意一下这里转译符的使用)
sed -n '/\/sbin\/nologin/p' /etc/passwd
# 使用正则表达式来进行一个匹配
sed -n '/^hdfs/p' /etc/passwd
# 5.从匹配pattern1的行,到匹配pattern2的行
# 这里就想但是是一个区间的模式(这里是从第一个匹配模式匹配到的行开始,到第二个匹配模式匹配到的行结束)
# 从以root开头的行开始到以man开头的行结束
sed -n '/^root/,/^man/p' /etc/passwd
# 6.指定给一个行号,直到匹配到pattern1对应的匹配行为止
sed -n '4,/^zoomdong/p' /etc/passwd
# 如果它找不到后面对应的匹配行,那么他会直接输出到最后一行里面去
# 7.和第6中情况正好相反
sed -n '/^root/,10p' /etc/passwd

综上所述,使用最多的还是第4和第5种情况。

ps:使用cat -n xxx可以打印文件并且对应出里面的行号对应的内容。

sed中的编辑命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
类别 编辑命令 含义
查询 p 打印
增加
a 行后追加
i 行前追加
r 外部文件追加
w 匹配行写入外部文件
删除 d 删除
修改
s/old/new  将行内的第一个old替换为new
s/old/new/g  将行内所有的old替换为new
s/old/new/2g 表示从第二个开始,后面的old全部替换为new
s/old/new/ig 加i参数时表示匹配时忽略大小写,g表示匹配到的全部替换

我们先将/etc目录下的passwd复制到当前目录下面来,cp /etc/passwd ./.

cat -n passwd 来查看一下这个文件下面的字符长度。

然后使用删除命令来进行一波删除:sed '1d' passwd

这个时候它会将结果打印到命令框中,但是它不会将真正的删除应用到原有的文件里面去。

sed -i '1d' passwd

使用head passwd,这个时候就会发现真正改变到原有的文件里面去了。

同时我们也可以使用sed -i '1,3d' passwd来对修改三行的代码。

接下来我们可以将passwd目录下不能登录的用户/sbin/nologin全部删除掉。

这个时候我们就需要使用正则表达式去进行一个匹配。

1
sed -i '/\/sbin\/nologin/d' passwd

这样再去查看的时候,就会发现以前的已经全部被删除了cat -n passwd

根据区间的匹配模式来进行删除:

sed -i '/^mail/,/^list/d' passwd

这样就可以删除掉以mail开头的行到以list开头的行之间的行。

上面就是删除命令的常用用法。

sed -i '/\/bin\/bash/a This is user which can login to system' passwd 

我们通过sed去匹配有/bin/bash的段落,然后通过a指令去在后面进行一个附加段落的添加(注意中间会有一段空格)。这一段会添加到下面一行。(行后追加的模式)

那么同样的,想在行的前面追加的话,把a修改成i就可以了

sed -i '/^sync/,/^nobody/i 行前添加一波内容' passwd

这样的话被匹配到的行的每一行前面都会添加上对应的内容。

这样我们使用cat -n passwd去查找的时候就会找到相对应的内容了。

新建一个叫做list的文件里面写上

1
2
First Line(YYYYYYY)
Second Line(XXXXXXXX)

然后拷贝cp /etc/passwd ./到当前目录下面来。

使用sed -i '/root/r list' passwd
首先它会查找passwd文件里面所有包含root的行,然后找到后会吧list里面的内容追加符合条件匹配的行的后面。

这样只要是包含了root的行后面都会添加上:

1
2
First Line(YYYYYYY)
Second Line(XXXXXXXX)

w的含义就是就是保存一波匹配到的内容去另外一个文件里面去:

sed -n '/\/bash\/bin/w ./test.txt' passwd

-n这里可以输出查看一波结果,这里会将passwd里面匹配到的结果直接复制到test.txt里面去。(有/bin/bash的用户都是可以登录到我们的用户系统里面去的)。

1
2
3
4
5
6
7
8
9
10
11
12
# 将passwd文件里面的/bin/bash全部替换为/BIN/BASH
sed -i 's/\/bin\/bash/\/BIN\/BASH/g' passwd
# 只替换第一个root为ROOT
sed -i 's/root/ROOT/' passwd
# 替换全部的root加个g即可
sed -i 's/root/ROOT/g' passwd
# 替换从第二个开始的root加个2g即可
sed -i 's/root/ROOT/2g' passwd
# 不用在乎替换的大小写的话直接加一个ig即可
sed -i 's/root/ROOT/ig'
# .在正则表达式里面匹配任意一个单个字符,这里表示匹配两个字符
sed -i 's/had..p/hadoops/g' passwd

其他编辑命令例如=是显示行号的意思

1
2
3
# 显示一波匹配的行号(但是不会显示内容)
sed -n '/\/sbin\/nologin/=' passwd
# 这里只会显示输出的行号的结果

假设我们现在有一个这样的文本str.txt:

1
2
3
4
hadAAp is bigdata frame
Spark hadBBp Kafka
Paper Of hadCCp
Google hadDDp

我们想在had..p后面加上一个s。这里我们就需要使用到反向引用(直接去引用我们所匹配到的内容)的操作了。

1
sed -i 's/had..p/&s/g' str.txt

这里的&表示我们前面匹配到的值,即为hadAAp,hadBBp,hadCCp。(原封不到匹配到的值)

这里还有另外一种方法,我们如果想在后面再加上一个O,可以使用这样的脚本(注意前面查找的正则要用括号括起来):

1
sed -i 's/\(had..ps\)/\1O/g' str.txt

现在str.txt的内容为:

1
2
3
4
hadoopsO is bigdata frame
Spark hadoopsO Kafka
Paper Of hadoopsO
Google hadoopsO

那我们将oops0替换为doop,也就是说现在我们只需要去替换部分的字符串。

1
sed -i 's/\(had\)...../\1doop/g' str.txt

如果我们想在sed里面使用变量的话,我们可以新建一个sedExample.sh的文件,里面内容是这样的:

1
2
3
4
5
6
7
8
#!/bin/bash
#
old_str=hadoop
new_str=HADOOP
# 注意有变量的时候一定要使用双引号
sed -i "s/$old_str/$new_str/g" str.txt

利用sed查询特定内容

pattern种类

  1. 8p
  2. 8,10p
  3. /regexp/p
  4. /regexp_1,/regexp_2/p
  5. 8,/regexp/p
  6. /regexp/,8p

练手命令

  1. 打印/etc/passwd中第20行的内容

    1
    sed -n '20p' passwd
  2. 打印/etc/passwd从第8行开始,到第15行结束的内容

    1
    sed -n '8,15p' passwd
  3. 打印/etc/passwd中从第8行开始,然后+5行的内容

    1
    sed -n '8,+5p' passwd
  4. 打印开头匹配hdfs字符串的内容

    1
    sed -n '/^hdfs/p' passwd
  5. 打印以root开头,hdfs行结尾的行的内容

    1
    sed -n '/^root,/^hdfs/p' passwd
  6. 打印从第8行开始,到含有/sbin/nologin的内容的行结束的内容

    1
    sed -n '8,/\/sbin\/nologin/p' passwd
  7. 打印从bin/bash的行到第五行的内容

    1
    sed -n '/\/bin\/bash/,5p' passwd
  • 脚本练习1

需求描述:处理一个类似MYSQL配置文件my.cnf的文本,示例如下:编写脚本实现以下功能:输出文件有几个段,并且针对每个段可以统计配置参数总个数(每个段里面都有自己的配置)

预想输出的结果:

1
2
3
4
5
6
1. client 2
2. server 12
3. mysqld 12
4. mysqld_safe 7
5. embedded 8
6. mysqld-5.5 9

首先要获取到所有的段 get_all segement

然后统计一波行数 cout_items_in_segment

则脚本为:

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
#!/bin/bash
#
FILE_NAME=/root/lessons/my.cnf
# 获取到所有的段
function get_all_segments
{
# 可以使用grep对文本进行一个处理
# grep -E "^\[" my.cnf
# 使用sed 加个管道|把[]替换掉(替换为空)
# 同时把结果输出出去,记得使用命令替换
echo "`sed -n '/\[.*\]/' $FILE_NAME | sed -e 's/\[//g' | -e 's/\]//g'`"
}
# 把段名参数传进去去拿参数
function count_items_in_segement
{
# 使用区间匹配sed -n "//,//p" xxx $1是传递进来的段名参数
# 同时过滤掉#开头的行和空号(以$开头的),还有[mysql],[server]这些信息
items=`sed -n "/\[$1\]/,/\[.*\]/p" $FILE_NAME | grep -v "^#" | grep -v ^$ | grep -v "\[.*\]"`
# 然后统计items的行数
index=0
for item in $items
do
index=`expr $index + 1`
done
echo $index
}
number=0
# 先得到所有的列表
for seg in `get_all_segments`
# 把段名作为参数传给count_items_in_segement
do
number=`expr $number + 1`
# 把seg作为参数过去,得到列表的数目
items_count=`count_items_in_segement $seg`
echo "$number: $seg $items_count"
done

利用sed来删除特定的内容

删除命令对照表可以看一波前面的内容。
用法总结:

  1. 1d
  2. 5,10d
  3. 10,+10d
  4. /pattern1/d
  5. /pattern1/,/pattern2/d
  6. /pattern1/, 20d
  7. 15,/pattern1/d

练习例子:

  1. 删除/etc/passwd中的第15行

    1
    sed -i '15d' /etc/passwd
  2. 第8-14行所有内容

    1
    sed -i '8,14d' /etc/passwd
  3. 删除不能登录的用户

    1
    sed -i '/\/sbin\/nologin/d' /etc/passwd
  4. 删除从mail开头到yarn开头的行的所有内容

    1
    sed -i '/^mail/,/^yarn/d' /etc/passwd
  5. 删除第一个不能登录的用户到第13行的所有内容

    1
    2
    # 一般不推荐使用这种模式
    sed -i '/\/sbin\/nologin/,13d' /etc/passwd
  6. 删除第五行到以ftp开头的内容

    1
    sed -i '5,/^ftp/d' /etc/passwd
  7. 删除以yarn开头到后所有行的内容

    1
    sed -i '/^yarn/,$' passwd

这里以nginx.conf为例子(可以参考服务器上面的nginx文件)
典型需求:

  1. 删除配置文件中所有注释行和空行

    1
    2
    # 删除空格然后删一个#(反正就是删有#号注释的行)
    sed -i '/[:blank:]*#/d' nginx.conf
  2. 在配置文件中所有不以#开头的前面添加*符号,注意:以#开头的行不添加。

    1
    2
    # 先匹配非#开头的行,*前面加一个转译
    sed -i 's/^[^#]/\*&/g' nginx.conf

使用sed修改文件内容

修改命令对照表参照以前。

head -1 passwd
查看某个文件的第一行

练习Demo:

  1. 修改/etc/passwd中第一行中第一个rootROOT

    1
    sed -i '1s/root/ROOT/' passwd
  2. 修改/etc/passwd中第五行到第十行中所有的/sbin/nologin/bin/bash

    1
    sed -i '5,10s/\/sbin\/nologin/\/bin\/bash/g' passwd
  3. 匹配到有/sbin/nologin的行,将匹配到行中的login改为LOGIN

    1
    sed -i '/\/sbin\/nologin/s/login/LOGIN/g' passwd
  4. 匹配出从root开头的行到行中包含mail的行,并将里面的bin改为HADOOP

    1
    sed -i '/^root/,/mail/s/bin/HADOOP/g' passwd
  5. 修改/etc/passwd中从匹配到以root开头的行,到第15行中所有的行,将这些行中的nologin修改为SPARK

    1
    sed -i '/^root/,15s/nologin/SPARK/g'
  6. 修改从15行开始,到以yarn开头的行,把bin替换为BIN

    1
    sed -i '15,/^yarn/s/bin/BIN/g'

利用sed追加文件内容

总结:

  1. a 在匹配行后面追加(append)

(1) passwd文件第10行后面追加Add Line Behind

1
sed -i '10a Add Line Behind' passwd

(2) passwd文件第10行到第20行,每一行后面都追加Test Line Behind

1
sed -i '10,20a Test Line Behind' passwd

(3) passwd文件匹配到/bin/bash的行后面追加”Insert Line For /bin/bash Behind”

1
sed -i '/\/bin\/bash/a Insert Line For /bin/bash Behind' passwd
  1. i 在匹配行的前面添加

(1) passwd文件匹配到以yarn开头的行,在匹配行前面追加Add Line Before

1
sed -i '/^yarn/i Add Line Before' passwd

(2) passwd文件每一行前面都追加”Insert Line Before Every Line”

1
sed -i 'i Insert Line Before Every Line' passwd
  1. r 把外部文件内容追加到特定行后面

(1) 将/etc/fstab 文件的内容追加到passwd文件的第20行后面

1
sed -i '20r /etc/fstab' passwd

(2) 将/etc/inittab文件内容追加到passwd文件匹配sbin/nologin行的后面

1
sed -i '/\/bin\/bash/r /etc/inittab' passwd

(3) 将/etc/vconsole.conf文件内容追加到passwd文件中特定行后面,匹配以ftp开头的行,到第18行的所有内容

1
sed -i '/^ftp/,18r /etc/vconsole.conf' passwd

  1. w 处理文件中匹配行的内容保存到外部文件中
    (1) 将passwd文件匹配到bin/bash的行追加到/tmp/sed.txt文件中
    1
    sed -i '/\/bin\/bash/w /tmp/sed.txt' passwd

(2) 将passwd文件从第10行开始,到匹配到hdfs开头的所有行内容追加到/tmp/sed-1.txt

1
sed -i '10,/^hdfs/w /tmp/sed-1.txt' passwd

-------------本文结束感谢您的阅读-------------