Linux常用命令--sed

摘要

  • sed命令使用说明

  • 本文基于CentOS8(x86_64)

sed

  • sed 是一种在线编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非你使用重定向存储输出。

  • sed 主要用来自动编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。

  • 语法

1
格式:sed [options] 'command' file(s)
  • 参数

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
功能参数	        解释
-n 关闭输出,和p一起使用只输出被sed处理的行
-e 多重编辑,且顺序会影响结果
-r 使用扩展正则
-i 将修改的内容覆盖到文档,使用时要小心
-f 指定一个sed脚本文件到命令行执行

范围表达式 解释
'2,5p' 打印2-5行
'2p;5p' 打印2和5行
'2,+5p' 打印第2行及其以下5行
'2~2p' 从第二行开始步长为2打印,即打印2,4,6,8……行
'/regexp/p' 打印正则表达式匹配出的行
'2,/aaa/p' 打印第2行到下一次出现aaa的行,如果aaa在第二行之前或者不存在则打印到行尾
'/aaa/,/bbb/p' 打印aaa所在的行到bbb所在的行,如果bbb在aaa之前或者不存在则打印到行尾

动作 解释
p 打印
d 删除
s 替换
a 当前行后追加
i 当前行前插入
c 将当前行替换
n 匹配行的下一行
y 替换,固定用法 y/abcd/ABCD/ 将a替换为A,b替换为B……
q 退出sed

1)删除:d命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 删除example文件的第二行
sed '2d' example
# 删除第二行和第五行
sed '2d;5d' example
# 删除第二行到第五行
sed '2,5d' example
# 删除第二行及其之后的五行
sed '2,+5d' example
# 删除example文件的第二行到末尾所有行
sed '2,$d' example  
# 删除example文件的最后一行
sed '$d' example
# 删除example文件所有包含test的行
sed '/test/d' example
# 所有在模板test和check所确定的范围内的行都被删除
sed '/test/,/check/d' example
# 从第一行开始,每间隔一行删除一行,这里表示删除1,3,5...行
sed '1~2d' example
# 删除匹配行的下一行,n表示下一行
sed '/test/{n;d}' example

# 删除变量指定的行,注意这里要使用双引号
sed "1,${lineNum}d" example

2)替换:s命令

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
# 替换第几处模式匹配的地方,这里2表示替换第二处匹配的地方
sed 's/test/mytest/2' example
n : 1~512 之间的数字,表示指定要替换的字符串出现第几次时才进行替换

# 在整行范围内把test(i不区分大小写)替换为mytest。如果没有g标记,则只有每行第一个匹配的test被替换成mytest
sed 's/test/mytest/ig' example
i : 不区分大小写
g : 对数据中所有匹配到的内容进行替换,不加g只匹配第一个

# (-n)选项和p标志一起使用表示只打印那些发生替换的行。也就是说,如果某一行开头的test被替换成mytest,就打印它
sed -n 's/^test/mytest/p' example 
-n : 关闭输出,和p标志一起使用表示只打印那些发生替换的行

# &符号表示替换字符串中被找到的部份。所有以192.168.0.1开头的行都会被替换成它自已加 localhost,变成192.168.0.1localhost
sed 's/^192.168.0.1/&localhost/' example 
& : 表示替换字符串中被找到的部份

# love被标记为1,所有loveable会被替换成lovers,而且替换的行会被打印出来。
sed -n 's/\(love\)able/\1rs/p' example 
() : 标记搜索内容,括号顺序按1,2...顺序被标记,注意这里需要被转义
# love被标记为1,所有loveable会被替换成lovers,打印所有行
sed -r 's/(^love)able/\1rs/' example 
-r : 支持扩展正则,这里()不需要被转义
# 所有loveable会被替换成loversbl,括号顺序按1,2...顺序被标记
sed -r 's/(^love)a(bl)e/\1rs\2/' example 

# 不论什么字符,紧跟着s命令的都被认为是新的分隔符,所以,“#”在这里是分隔符,代替了默认的“/”分隔符。表示把所有10替换成100
sed 's#10#100#g' example 

# -i直接替换源文件,慎重使用
sed -i 's/test/mytest/ig' example 
-i : 直接替换源文件,慎重使用

# w标记会将匹配后的结果保存到指定文件中,注意这里text.txt中只会保留那些发生替换的行
sed 's/test/mytest/w test.txt' example  ====> sed -n 's/test/mytest/p' example > test.txt
w : 会将匹配后的结果保存到指定文件中

# 替换变量指定的行,注意这里要使用双引号,如果替换内容为双引号,要进行转义
# 这里将第一行到指定的行中的"}替换为"},
sed -r "1,${lineNum}s/(\"})/\1,/" example 

3)选定行的范围:逗号

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
# 所有在模板test和check所确定的范围内的行都被打印 
sed -n '/test/,/check/p' example
# 按连续内容截取,这里还是按关键字搜索匹配,这里假设每行都有序号并且连续
sed -n '/5518/,/5524/p' example  

# grep也有类似方法:
countnum=$[ $endnum - $startnum + 1  ]
grep "$startnum" file -A $countnum 

# 打印文件的第二行
sed -n '2p' example
# 打印文件的第二行和第四行
sed -n '2p;4p' example
# 打印第1行到第100百行
sed -n '1,100p' example  
# 打印从第五行开始到第一个包含以test开始的行之间的所有行
sed -n '5,/^test/p' example
# 打印从第一个包含以test开始的行到第五行之间的所有行
sed -n '/^test/,5p' example
# 打印从第一个包含以test开始的行到最后一行之间的所有行
sed -n '/^test/,$p' example
# 打印包含java或者linux的行
sed -r -n '/java|linux/p' example

# 从第一行开始,间隔2行打印,这里表示打印1,4,7,10...行
sed -n '1~3p' example

# 打印以java开头的所有行
sed -n '/^java/p' example
# 打印以java开头的所有行的下一行
sed -n '/^java/{n;p}' example

# 对于模板test和check之间的行,每行的末尾用字符串sed test替换
sed '/test/,/check/s/$/sed test/' example
# 第2到5行,每行的末尾用字符串sed test替换
sed '2,5s/$/sed test/' example
# 第2到最后一行,每行的末尾用字符串sed test替换
sed '2,$s/$/sed test/' example

4)多点编辑:e命令

1
2
3
4
5
6
7
8
9
10
# (-e)选项允许在同一行里执行多条命令。如例子所示,第一条命令删除1至5行,第二条命令用check替换test。命令的执行顺序对结果有影响。如果两个命令都是替换命令,那么第一个替换命令将影响第二个替换命令的结果
sed -e '1,5d' -e 's/test/check/' example

# 一个比-e更好的命令是--expression。它能给sed表达式赋值。
sed --expression='s/test/check/' --expression='/love/d' example


totalLine=`cat title_list.json | wc -l`
lastLineBefore1=$(($totalLine-1))
sed -i -r -e "1,${lastLineBefore1}s/(\"})/\1,/" -e '1d' -e '2i {[' -e '$a ]}' title_list.json

5)追加行或替换行:a、i、c和r

  • a 命令表示在指定行的后面附加一行,i 命令表示在指定行的前面插入一行

1
2
3
4
5
6
7
8
9
10
11
12
# 将hello wordl 插入到第二行上面
sed '2i hello world' example
i : 表示在指定行的前面插入一行,$i 表示最后一行前插入

# 将hello wordl 插入到第二行下面
sed '2a hello world' example
a : 表示在指定行的后面插入一行,$a 表示最后一行后插入

# 也可以同时插入多行内容,使用反斜线即可,这里表示在第二行下面添加两行内容
sed '2a\
> hello world\
> i love you' example
  • c 命令表示将指定行中的所有内容,替换成该选项后面的字符串

1
2
3
4
5
6
7
8
9
10
# 将匹配到的所有含有hello的行替换为hello world
sed '/hello/c hello world' example

# 将hello world 替换第二行
sed '2c hello world' example
c : 替换行内容
# 同样支持替换为多行
sed '2c\
> hello world\
> i love you' example
  • r 命令用于将一个独立文件的数据插入到当前数据流的指定位置

1
2
3
4
# 将text.txt中的内容插入到example文件的第二行下面
sed '2r test.txt' example
# 将text.txt中的内容插入到example文件的末尾
sed '$r test.txt' example

6)处理单个字符:y

1
2
3
4
5
6
# 将所有以hello开头的行中的 a替换为A,b替换为B,c替换为C,d替换为D
sed '/^hello/y/abcd/ABCD/' example

$ echo "This 1 is a test2 of 1 try3." | sed 'y/123/456/'
This 4 is a test5 of 4 try6.
y : 字符全局替换,原字符与替换字符的长度必须相同

7)指定行区间

  • 默认情况下,sed 命令会作用于文本数据的所有行,如果只想将命令作用于特定行或某些行,需要明确指定行区间范围。

  • 行区间可以通过如下两种方式进行限定:

    1.以数字形式指定行区间
    2.用文本模式指定具体行区间

  • 行区间放在命令的前面,具体格式如下:

1
2
3
4
5
6
7
[行区间]脚本命令

或者

行区间 {
多个脚本命令,分号分隔
}
  • 示例:以数字形式指定行区间

1
2
3
4
5
6
7
8
# 替换第二行中的内容
sed '2s/dog/cat/' test.txt
# 替换第二行到第四行中的内容
sed '2,4s/dog/cat/' test.txt
# 替换第二行到文件最后一行中的内容
sed '2,$s/dog/cat/' test.txt
# 从第一行开始,每间隔一行进行替换,1,3,5,7...
sed '1~2s/dog/cat/' test.txt
  • 示例:用文本模式指定具体行区间

1
2
3
4
5
6
7
8
9
10
11
12
# 替换内容中包含pig的所有行的内容
sed '/pig/s/dog/cat/' test.txt

# 正则匹配限制范围,{}指定多个脚本,这个命令的作用是将 h1Helloh1 转换为 <h1>Hello</h1>
sed '/h[0-9]/{s//\<&\>/1;s//\<\/&\>/2}' test.txt
# 也可以将命令写到文件中,通过 -f 参数指定
sed -f sed.txt test.txt
cat sed.txt
/h[0-9]/{
s//\<&\>/1
s//\<\/&\>/2
}

小贴士

  • 一、分隔符
    在sed命令中,/字符被用作分隔符,用于分隔sed命令中的参数。这个分隔符是可以被其他符号替换的。
    比如,以下三个命令的效果是一样的:
1
2
3
sed 's/test/mytest/2' file  # 用 / 做分隔符
sed 's|test|mytest|2' file # 用 | 做分隔符
sed 's#test#mytest#2' file # 用 # 做分隔符
  • 二、引用变量
    有些时候,我们需要在sed命令中引用变量,比如:
1
2
3
app_res_data="123/456"
# 这里我们使用 # 作为分隔符,引用变量 $app_res_data
sed -i "s#^App\.Res\.Data.*#$app_res_data#"
  • 三、显示行号

1
2
# 这种方式会给每一行的上一行增加一行,显示行号
sed = file
1
2
3
4
5
6
7
8
9
10
11
$ cat a.txt
hello
hello
hello
$ sed = a.txt
1
hello
2
hello
3
hello

实战示例

  • 统计Nginx日志全天总的请求数(访问量)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 常规的实现方式可以使用cat或wc -l命令
cat access_20231130.log |wc -l
wc -l access_20231130.log |awk '{print $1}'

# 接下来我们使用awk grep 和 sed 来分别实现
awk '{print $0}' access_20231130.log | wc -l
# 只打印最后一行的行号,这里先显示初始化NR为0,再显示最后一行的行号
awk 'BEGIN {NR==0} END {print NR}' access_20231130.log
# 实际上NR默认初始化就是0,可以简写为
wk 'END {print NR}' access_20231130.log

# sed = access_20231130.log 这种方式会给每一行的上一行增加一行,显示行号
# tail -2 就是只打印最后两行,因为此时总的行号在倒数第二行
# awk 'NR==1 {print $0}' 只打印第一行,也就是总行号
sed = access_20231130.log | tail -2 | awk 'NR==1 {print $0}'


# -a:显示所有匹配行,包括空行。
# -i:忽略大小写。
# -c:统计匹配行的数量。
# -E:使用扩展正则表达式。
# "":空模式,表示匹配所有行。
grep -aicE "" access_20231130.log
  • 统计Nginx日志全天09:00-11:00之间总的请求数(访问量)

1
2
3
4
5
# 范围查询,注意转义
awk '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/' access_20231130.log | wc -l

# -n:关闭输出,和p一起使用只输出被sed处理的行
sed -n '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/p' access_20231130.log | wc -l
  • 统计Nginx日志全天09:00-11:00之间总的请求数(访问量),并将请求的IP地址打印出来

1
2
sed -n '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/p' access_20231130.log | awk '{print $1}'
sed -n '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/p' access_20231130.log | cut -d" " -f1
  • 统计Nginx日志全天09:00-11:00之间总的请求数(访问量),并将请求的IP地址访问排前20名打印出来

1
sed -n '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/p' access_20231130.log | awk '{print $1}' | sort -n | uniq -c | sort -nr | head -20
  • 统计Nginx日志全天09:00-11:00之间总的请求数(访问量),并将请求的IP地址访问次数超过9000次加入Linux黑名单

1
2
3
4
5
6
7
8
9
10
# 将IP地址访问次数超过9000打印出来
sed -n '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/p' access_20231130.log | awk '{print $1}' | sort -n | uniq -c | sort -nr | awk '{if(($1>=9000)) {print $2}}'

# 将IP地址访问次数超过9000打印出来,将其加入黑名单
for ip in $(sed -n '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/p' access_20231130.log | awk '{print $1}' | sort -n | uniq -c | sort -nr | awk '{if(($1>=9000)) {print $2}}');do iptables -t filter -A INPUT -s $ip/32 -m tcp -p tcp --dport 80 -j DROP;done
# 或者
sed -n '/30\/Nov\/2023:09:00/,/30\/Nov\/2023:11:00/p' access_20231130.log | awk '{print $1}' | sort -n | uniq -c | sort -nr | awk '{if(($1>=9000)) {print $2}}' | xargs -I {} iptables -t filter -A INPUT -s {}/32 -m tcp -p tcp --dport 80 -j DROP

# 查看结果
iptables -t filter -L -n --line-numbers| grep -aiE "DROP|80"