11sed命令

0 阅读24分钟

sed命令

基础实践

基础语法

场景

shell脚本虽然功能很多,但是它最常用的功能还是处理文本文件,尤其是在正常的业务操作流程场景中,比如检查日志文件、读取配置、处理数据等,虽然我们能够使用echo、cat、<<、>>、|等符号实现文件内容的操作,但是整个过程有些繁琐。所以我们需要一种更为轻便的文本编辑工具,sed就是其中的一种

简介

sed(Stream EDitor) 属于一种数据流式的行文件编辑工具。因为它编辑文件的时候,在内存中开辟一块额外的模式空间(pattern space),然后以行为单位读取文件内容到该空间中,接着sed命令处理该空间中的内容,默认在当前终端界面打印内容,然后清空模式空间内容,再来读取第二行内容,依次循环下去

image.png

作用:用来自动编辑一个或多个文件,简化对文件的反复操作,编写转换程序等 参考:www.gnu.org/software/se…

语法格式

sed [OPTION]... {script-only-if-no-other-script} [input-file]...

sed [参数] '<匹配条件> [动作]' [文件名]动作就是命令

注意:匹配条件和动作两侧有单引号,动作可以有多个,彼此间使用分号(;)隔开,比如 '2p;4p'

选项说明
-n, --quiet, --silent不输出模式空间内容到屏幕,即不自动打印所有内容
-e script, --expression=script将脚本添加到要执行的命令中
-f script-file, --file=script-file将脚本文件的内容添加到要执行的命令中,即从指定文件中读取编辑文件的"匹配条件+动作"
--follow-symlinks就地处理时遵循符号链接
-i[SUFFIX], --in-place[=SUFFIX]就地编辑文件(如果提供SUFFIX,则进行备份);-i.bak复制文件原内容到备份文件,然后对原文件编辑
-c, --copy在-i模式下切换文件时使用复制而不是重命名
-b, --binary不进行任何操作;与WIN32/CYGWIN/MSDOS/EMX兼容(以二进制模式打开文件(CR+LFs不受特殊处理))
-l N, --line-length=N为`l'命令指定所需的换行长度
--posix禁用所有GNU扩展
-r, --regexp-extended在脚本中使用扩展正则表达式,即支持使用扩展正则表达式
-s, --separate将文件视为独立的,而不是单个连续的长流
-u, --unbuffered从输入文件加载最少量的数据,并更频繁地刷新输出缓冲区
-z, --null-data用NUL字符分隔行
--help展示帮助信息
--version展示版本信息

没有任何选项表示sed的操作效果,实际上不对文件进行编辑,缓存区所有信息都显示

如果没有给定-e, --expression, -f, 或 --file选项,则将第一个非选项参数作为sed脚本进行解释。所有剩余的参数都是输入文件的名称;如果没有指定输入文件,则读取标准输入

注意:mac版本的bash中使用 -i参数,必须在后面单独加个东西-i '';-ni 危险选项,会清空文件

命令概要

  • Zero-address ``commands''
命令说明
: labelb和t命令的标签
#comment注释一直延伸到下一个换行符(或-e脚本片段的末尾)
}{}块的右括号
  • Zero- or One- address commands
命令说明
=打印当前行号(为模式空间中的行打印行号)
a \ text附加文本,每个嵌入的换行符前面都有一个反斜杠(在匹配到的内容下一行增加内容,支持\n实现多行追加)
i \ text插入文本,每个嵌入的换行符前面都有一个反斜杠(在匹配到的内容当前行增加内容,相当于在前面插入行增加内容)
q [exit-code]立即退出sed脚本,不处理任何输入,除非没有禁用自动打印,否则将打印当前模式空间。退出代参数是一个GNU扩展
Q [exit-code]立即退出sed脚本,不再处理任何输入。这是一个GNU扩展
r filename附加从filename读取的文本(读取指定文件的文本至模式空间中)
R filename追加从filename读取的一行,该命令的每次调用都从文件中读取一行,这是一个GNU扩展
  • Commands which accept address ranges
命令说明
{开始一个命令块(以}结束)
b label跳转到标签;如果省略了label,则跳转到脚本的末尾
c \ text将选定的行替换为文本,其中每个嵌入的换行符前面都有一个反斜杠(替换匹配的行)
d删除模式空间。开始下一轮循环
D如果模式空间不包含换行符,就像发出了d命令一样开始一个正常的新循环。否则,删除模式空间中直到第一个换行符的文本,并使用生成的模式空间重新开始循环,而不读取新的输入行
h/H复制/追加模式空间到保留空间
g/G复制/追加保留空间到模式空间
l以“视觉清晰”的形式列出当前行
l width以“视觉清晰”的形式列出当前行,以宽度字符分隔。这是一个GNU扩展
n/N读取/追加下一行输入到模式空间
p打印当前模式空间
P打印到当前模式空间的第一个嵌入换行符为止
s/regexp/replacement/尝试将regexp与模式空间匹配。如果成功,替换匹配的部分。替换可以包含特殊字符&来指代匹配的模式空间的那一部分,特殊转义\1到\9来指代regexp中相应的匹配子表达式(替换匹配到的内容)
t label如果自读取最后一个输入行以来以及自最后一个t或t命令以来,s///已成功完成替换,则跳转到标签;如果省略了标签,则跳转到脚本末尾
T label如果自读取最后一个输入行以来以及自最后一个t或t命令以来,没有s///成功替换,则跳转到标签;若省略了标签,则跳转到脚本末尾。这是一个GNU扩展
w filename将当前模式空间写入filename
W filename将当前模式空间的第一行写入filename。这是一个GNU扩展
x交换保留空间和模式空间的内容
y/source/dest/将模式空间中出现的“source”字符转换为“dest”字符

注意:上面的动作(命令)应该在选项为-i的时候使用,不然的话不会有效果

Addresses

Sed命令可以不带地址,在这种情况下,命令将对所有输入行执行;对于一个地址,在这种情况下,命令将只对与该地址匹配的输入行执行;或者有两个地址,在这种情况下,命令将对匹配范围(从第一个地址开始持续到第二个地址的范围)内的所有输入行执行。关于地址范围需要注意三件事:语法是addr1,addr2(即,地址由逗号分隔);addr1匹配的行将永远被接受,即使addr2选择了更早的行;如果addr2是regexp,则不会针对addr1匹配的行对其进行测试

在地址(或地址范围)之后,在命令之前,可以插入一个!,它指定只有在地址(或地址范围)不匹配时才能执行命令

支持以下地址类型

地址说明
number只匹配指定的行号(除非在命令行中指定了-s选项,否则该行号会在文件中累积递增)
first~step从第一行开始匹配每一步的行。例如,sed -n 1~2p将打印输入流中的所有奇数行,地址2~5将从第二行开始每隔五行匹配一次。第一个可以是零,在这种情况下,sed的操作就好像它等于step一样(这是一个扩展)
$匹配最后一行
/regexp/匹配正则表达式regexp的行
\cregexpc匹配正则表达式regexp的行。c可以是任何字符
0,addr2从"匹配的第一个地址"状态开始,直到找到addr2。这类似于1,addr2,不同之处在于,如果addr2与输入的第一行匹配,则0,addr2形式将位于其范围的末尾,而1,addr2形式仍处于其范围的开头。只有当addr2是正则表达式时,这才有效
addr1,+N将匹配addr1和addr1后面的N行
addr1,~N将匹配addr1和addr1之后的行,直到输入行号为N的倍数的下一行
匹配条件分为两种:数字行号或者关键字匹配
数字行号:
	空 表示所有行				n 表示第n行				$ 表示末尾行
	n,m 表示第n到m行内容		n,+m 表示第n到n+m行
	~步进	1~2 表示奇数行		2~2 表示偶数行
	!  模式空间中匹配行取反处理

关键字匹配格式:
	'/关键字/'
    注意:
        隔离符号 / 可以更换成 @、#、!等符号
        根据情况使用,如果关键字和隔离符号有冲突,就更换成其他的符号即可。
        /关键字1/,/关键字2/ 表示关键字1所在行到关键字2所在行之间的内容
        n,/关键字2/ 表示从第n行到关键字2所在行之间的内容

简单实践

# 准备文件
cat > sed.txt << EOF
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed7 sed8 sed9
EOF

# 实践1-打印信息

# 默认打印信息
sed '2p' sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed4 sed5 sed6			# 这一行才是操作的内容,其他的都是缓存区自动输出
nihao sed7 sed8 sed9

# 打印第2行,不输出缓存区默认的其他信息
sed -n '2p' sed.txt
nihao sed4 sed5 sed6

# 打印第1,3行,不输出缓存区默认的其他信息
sed -n '1p;3p' sed.txt
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

# 打印网卡信息
ifconfig ens33 | sed -n '2p'
        inet 192.168.91.101  netmask 255.255.255.0  broadcast 192.168.91.255

# 打印磁盘信息
df | sed -n '/^\/dev\/sd/p'
/dev/sda1                 1038336  155768    882568  16% /boot

# 实践2-匹配内容打印

# 打印包含sed4的行
sed -n '/sed4/p' sed.txt
nihao sed4 sed5 sed6

# 打印奇数行
sed -n '1~2p' sed.txt
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

# 打印偶数行
sed -n '0~2p' sed.txt
nihao sed4 sed5 sed6

# 实践3-文件编辑

# -e实现多次文件编辑动作
sed -n -e '1p' -e '3p' sed.txt
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

# 将文件操作命令输出到一个文件
echo -e "1p\n3p" > sed_script
# 借助文件里面的命令实现文件编辑
sed -n -f sed_script sed.txt
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

# 实践4-其他信息显示
# 取反显示
sed -n '2!p' sed.txt
nihao sed1 sed2 sed3
nihao sed7 sed8 sed9

# 查看内容属于第几行
sed -n '/sed4/=' sed.txt
2

内容替换

sed的文本替换动作是使用频率最高的一种样式,它的基本表现样式如下:

命令格式:
	sed -i [替换格式] [文件名]
	源数据 | sed -i [替换格式]
	
注意:替换命令的写法
	's###'  --->  's#原内容##' ---> 's#原内容#替换后内容#'
	隔离符号 / 可以更换成 @、#、!等符号
表现样式:
    样式一:替换指定匹配的内容
        sed -i '行号s#原内容#替换后内容#列号' [文件名]
        echo "源数据" | sed -i '行号s#原内容#替换后内容#列号'
    样式二:替换所有的内容
        sed -i 's#原内容#替换后内容#g' [文件名]
    	echo "源数据" | sed -i 's#原内容#替换后内容#g'
    样式三: 替换指定的内容
    	sed -i '行号s#原内容#&新增信息#列号' [文件名]
    	- 这里的&符号代表源内容,实现的效果是 '原内容+新内容'

简单实践

# 实践1-替换每行首个匹配内容,格式:sed -i 's#原内容#替换后内容#' 文件名
# 替换每行的第1个sed为SED
sed -i 's#sed#SED#' sed.txt

cat sed.txt
nihao SED1 sed2 sed3
nihao SED4 sed5 sed6
nihao SED7 sed8 sed9

# 实践2-替换全部匹配内容,格式:sed -i 's#原内容#替换后内容#g' 文件名
# 替换全部sed为SED
sed -i 's#sed#SED#g' sed.txt

cat sed.txt
nihao SED1 SED2 SED3
nihao SED4 SED5 SED6
nihao SED7 SED8 SED9

# 关于全部替换还有另外一种命令叫直接转换 y
sed 'y/SED/sed/' sed.txt
nihao sed1 sed2 sed3
nihao sed4 sed5 sed6
nihao sed7 sed8 sed9

# 实践3-指定行号替换首个匹配内容,格式:sed -i '行号s#原内容#替换后内容#' 文件名
# 替换第2行的首个SED为sed
sed -i '2s#SED#sed#' sed.txt

cat sed.txt
nihao SED1 SED2 SED3
nihao sed4 SED5 SED6
nihao SED7 SED8 SED9

# 实践4-指定列号替换匹配内容,格式:sed -i 's#原内容#替换后内容#列号' 文件名
# 替换每行的第2个SED为sed
sed -i 's#SED#sed#2' sed.txt

cat sed.txt
nihao SED1 sed2 SED3
nihao sed4 SED5 sed6
nihao SED7 sed8 SED9

# 实践5-指定行号列号匹配内容,格式:sed -i '行号s#原内容#替换后内容#列号' 文件名
# 替换第3行的第2个SED为sed
sed -i '3s#SED#sed#2' sed.txt

cat sed.txt
nihao SED1 sed2 SED3
nihao sed4 SED5 sed6
nihao SED7 sed8 sed9

# 实践6-综合实践
# 借助正则的分组功能实现ip地址获取
ifconfig ens33 | sed -n '2p' | sed -r 's#.*inet (.*) net.*#\1#'
192.168.91.101

ifconfig ens33 | sed -n '2p' | sed -r 's#.*inet ##' | sed -r 's# net.*##'
192.168.91.101

# 借助正则的分组功能实现信息的精确获取
echo '/etc/sysconfig/network' | sed -r 's#(.*\/)([^/]+\/?$)#\2#'
network

echo '/etc/sysconfig/network' | sed -r 's#(.*\/)([^/]+\/?$)#\1#'
/etc/sysconfig/

增加操作

追加实践

基本语法

作用:
	在指定行号的下一行增加内容
格式:
	sed -i '行号a\增加的内容' 文件名
注意:
    如果增加多行,可以在行号位置写个范围值,彼此间使用逗号隔开,例如
    sed -i '1,3a\增加内容' 文件名
# 实践1-基于行号实践
# 指定行号增加内容
sed -i '2a\zengjia-2' sed.txt

cat sed.txt
nihao SED1 sed2 SED3
nihao sed4 SED5 sed6
zengjia-2
nihao SED7 sed8 sed9

# 指定1~3每行都增加内容
sed -i '1,3a\tongshi-2' sed.txt

cat sed.txt
nihao SED1 sed2 SED3
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

插入实践

基本语法

作用:
	在指定行号的当行增加内容
格式:
	sed -i '行号i\增加的内容' 文件名
注意:
    如果增加多行,可以在行号位置写个范围值,彼此间使用逗号隔开,例如
    sed -i '1,3i\增加内容' 文件名
# 实践1-基于行号实践
# 指定行号增加内容
sed -i '1i\insert-1' sed.txt

cat sed.txt
insert-1
nihao SED1 sed2 SED3
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

# 指定1~3每行都增加内容
sed -i '1,3i\insert-2' sed.txt

cat sed.txt
insert-2
insert-1
insert-2
nihao SED1 sed2 SED3
insert-2
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

删除替换

删除实践

基本语法

作用:
	指定行号删除
格式:
	sed -i '行号d' 文件名
注意:
    如果删除多行,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3d' 文件名
# 实践1-基于行号实践
# 删除第4行内容
sed -i '4d' sed.txt

cat sed.txt
insert-2
insert-1
insert-2
insert-2
tongshi-2
nihao sed4 SED5 sed6
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

# 删除多行(1-6行)内容
sed -i '1,6d' sed.txt

cat sed.txt
tongshi-2
zengjia-2
tongshi-2
nihao SED7 sed8 sed9

替换实践

基本语法

作用:
	指定行号进行整行替换
格式:
	sed -i '行号c\内容' 文件名
注意:
    如果替换多行,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3c\内容' 文件名
# 实践1-基于行号实践
# 替换第3行内容
sed -i '3c\tihuan-1' sed.txt

cat sed.txt
tongshi-2
zengjia-2
tihuan-1
nihao SED7 sed8 sed9

# 指定1~3行都替换成一行内容
sed -i '1,3c\tihuan-3' sed.txt

cat sed.txt
tihuan-3
nihao SED7 sed8 sed9

加载保存

加载实践

基本语法

作用:
	加载文件内容到指定行号的位置
格式:
	sed -i '行号r 文件名1' 文件名
注意:
    如果在多行位置加载,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3r 文件名1' 文件名
# 实践1-基于行号实践
# 加载第3行内容
sed -i '2r sed.txt' sed.txt

cat sed.txt
tihuan-3
nihao SED7 sed8 sed9
tihuan-3
nihao SED7 sed8 sed9

# 指定内容文件,加载到2-4行下面
sed -i '2,4r sed_script' sed.txt

cat sed_script
1p
3p

cat sed.txt
tihuan-3
nihao SED7 sed8 sed9
1p
3p
tihuan-3
1p
3p
nihao SED7 sed8 sed9
1p
3p

保存实践

基本语法

作用:
	指定行号保存到其他位置
格式:
	sed -i '行号w 文件名' 文件名
注意:
    如果多行保存,可以在行号位置多写几个行号,彼此间使用逗号隔开,例如
    sed -i '1,3w 文件名' 文件名
    文件名已存在,则会覆盖式增加
# 实践1-基于行号实践
# 保存第2行内容
sed -i '2w sed_test' sed.txt

cat sed_test
nihao SED7 sed8 sed9

# 指定2~4行内容保存到一个文件中
sed -i '2,4w sed_test' sed.txt

cat sed_test
nihao SED7 sed8 sed9
1p
3p

进阶实践

匹配进阶

我们之前的所有操作基本上都是基于行的操作,其实本质上还有另外一些操作 -- 基于内容的操作。语法格式如下:

内容匹配:
	'/关键字内容/' 匹配
	'/关键字内容/!' 不匹配(上面的匹配取反)
    注意:
        隔离符号 / 可以更换成 @、#、!等符号
        根据情况使用,如果关键字和隔离符号有冲突,就更换成其他的符号即可。
        /关键字1/,/关键字2/ 表示关键字1所在行到关键字2所在行之间的内容
        n,/关键字2/ 表示从第n行到关键字2所在行之间的内容
        /关键字1/,n, 表示从关键字1所在行到第n行之间的内容
        /关键字1/,+n, 表示从关键字1所在行到(所在行+n行)之间的内容

简单实践

# 实践1-内容的简单匹配显示

# 查看匹配的内容
sed -n '/send/p' nginx.conf
    sendfile        on;

# 匹配内容间的多行信息
sed -n '/send/,/server/p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;

    server {

# 查看匹配内容到第6行的内容
sed -n '/send/,6p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;

# 查看第1行到匹配行的内容
sed -n '1,/send/p' nginx.conf
#user  nobody;
worker_processes  1;

http {
    sendfile        on;

# 查看匹配内容和下面三行的内容
sed -n '/send/,+3p' nginx.conf
    sendfile        on;
    keepalive_timeout  65;

    server {

# 通过 !p 去除空行匹配
sed -n '/^$/!p' nginx.conf
#user  nobody;
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

# 借助分组功能,实现多信息的剔除
sed -rn '/^(#|$)/!p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

# 实践2-分组信息显示

# 获取指定文件所在的路径信息
echo "/etc/sysconfig/network" | sed -r 's#(^/.*/)([^/]+/?)#\1#'
/etc/sysconfig/

# 获取指定文件名称
echo "/etc/sysconfig/network" | sed -r 's#(^/.*/)([^/]+/?)#\2#'
network

# 获取ip地址
ifconfig ens33 | sed -nr "2s/[^0-9]+([0-9.]+).*/\1/p"
192.168.91.101

# 获取MAC地址
ifconfig ens33 | sed -nr "4s/[^0-9]+([0-Z:]+).*/\1/p"
00:0c:29:2c:2e:d5

修改实践

多点操作

我们可以借助 '动作1;动作2' 或者 -e '动作1' -e '动作2' 的方式实现多操作的并行实施

# 实践1-内容的过滤编辑

# 不显示所有空行和注释信息
sed '/^#/d;/^$/d' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

# 先剔除空行,然后不显示所有只包含注释的行
sed -rn '/^$/d;/^[[:space:]]*#/!p' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

# 实践2-借助于 i.bak 方式对有效信息进行过滤
# 编辑文件的时候,原内容备份到一个额外的文件
sed -i.bak '/^#/d;/^$/d' nginx.conf

cat nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

grep '#' nginx.conf.bak
#user  nobody;

增改实践

# 实践1-借助于&符号实现内容的扩充式更改编辑

# 查看原内容
head -n 1 /etc/passwd
root:x:0:0:root:/root:/bin/bash

# 对原内容进行扩充替换
head -n 1 /etc/passwd | sed -n 's/root/&user/1p'
rootuser:x:0:0:root:/root:/bin/bash

head -n 1 /etc/passwd | sed -n 's/root/&user/gp'
rootuser:x:0:0:rootuser:/rootuser:/bin/bash

# 实践2-借助于s实现内容的替换式更改编辑

# 获取没有被注释的信息(不包括空行)
sed -rn '/^(#|$)/!p' /etc/fstab
/dev/mapper/centos-root /                       xfs     defaults        0 0
UUID=f642f98f-d764-4994-9bb2-a2af0e3b86e8 /boot                   xfs     defaults        0 0
/dev/mapper/centos-swap swap

# 将所有没有注释的信息注掉(不包括空行)
# sed -rn '/^(#|$)/! s/^/#/p' /etc/fstab
sed -rn '/^(#|$)/!s/^/#/p' /etc/fstab
#/dev/mapper/centos-root /                       xfs     defaults        0 0
#UUID=f642f98f-d764-4994-9bb2-a2af0e3b86e8 /boot                   xfs     defaults        0 0
#/dev/mapper/centos-swap swap                    swap    defaults        0 0

# 实践3-借助于 i|a 对文件进行 插入|追加 式更改编辑

# 基于内容匹配相关信息并打印
sed -n '/listen/p' nginx.conf
        listen       8000;

# 基于内容匹配追加1行内容
sed '/listen/a\\tlisten\t\t80;' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       8000;
        listen          80;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
# 基于内容匹配插入2行内容 -- 借助于\n的换行功能,将1行变成两行
sed '/listen/i\\tlisten\t\t80;\n\tlisten\t\t8080;' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen          80;
        listen          8080;
        listen       8000;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

# 实践4-借助于 环境变量和s|c 对文件进行 修改|替换 式更改编辑

# 定制环境变量
port=8080
# 这里涉及到环境变量的解读,不要被单引号转义了
# 使用多点修改,使用双引号时c后面的反斜杠需要转义,c后面可以有空格
# sed -r -e "s/listen.*;/listen\t$port;/" -e "/server_name/c\\\tserver_name $(hostname):$port;" nginx.conf
sed -r -e "s/listen.*;/listen\t$port;/" -e '/server_name/c\\tserver_name '$(hostname):$port';' nginx.conf
worker_processes  1;
http {
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen  8080;
        server_name test:8080;
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

高阶用法1

基础知识

对于sed命令来说,除了我们经常使用的模式空间之外,它还支持一个叫暂存空间(Hold Space)的模式,所谓的暂存空间,也就是说,将模式空间中的数据,临时保存到暂存空间,从而实现更为强大的功能

相关业务逻辑流程如下:

image.png

空间解读:
	缓存空间用于sed的内容模式匹配,一般称为模式空间
		- 模式空间内的信息可以输出到终端界面,除非模式空间的内容被删除或取消打印导致模式空间清空
	附加于缓存空间的附加缓存空间,一般称为暂存空间
		- 通过相关命令可以在模式空间信息清零之前,暂存到附加缓存空间,便于后续使用
	两个空间之间,可以基于一些高阶命令实现信息的传递
常见的高阶命令  
    n 读取匹配到的行的下一行覆盖至模式空间
    N 读取匹配到的行的下一行追加至模式空间
    d 删除模式空间中的行
    D 如果模式空间包含换行符,则循环删除换行符前的内容,直至不包含任何换行符后,执行后续d操作

简单实践

# 实践1-模式空间覆盖

# 查看逐行读取的信息
seq 6 | sed -n "p"
1
2
3
4
5
6

# n 读取匹配到的行的下一行覆盖至模式空间
# 第一次读的是1,"n;"的作用是读取2,然后覆盖模式空间的1
seq 6 | sed -n "n;p"
2
4
6

# 2次n代表读取到第3行,将前的内容覆盖
seq 6 | sed -n "n;n;p"
3
6

# 3次n代表读取到第4行,将前面的内容覆盖
seq 6 | sed -n "n;n;n;p"
4

# 获取匹配内容的下一行,覆盖匹配的内容
# /2/ 代表的是匹配的2内容,然后{} 代表一个表达式区域,n;p 代表下一行覆盖式打印
seq 6 | sed -n "/2/{n;p}"
3

# 实践2-模式空间清零

# n 读取匹配到的行的下一行覆盖至模式空间
# 第一次读的是1,"n;"的作用是读取2,然后覆盖模式空间的1
seq 6 | sed -n "n;p"
2
4
6

# d 删除模式空间中的行
# n每覆盖一次,都用d删除一次,最终导致不会输出任何内容
seq 6 | sed -n "n;d"

# 实践3-模式空间扩容

# 查看默认的信息输出
seq 6
1
2
3
4
5
6

# N 读取匹配到的行的下一行追加至模式空间
# 第一次读的是1,"N;"的作用是读取2,然后追加到模式空间的1的后面;然后使用s将换行\n替换为空,实现1和2的合并
seq 6 | sed 'N;s/\n//'
12
34
56

# 通过-e,多一个N,就相当于为模式空间扩容了一个位置
# 1个空间+2个扩容 一共三个空间内容
seq 6 | sed -e 'N;s/\n//' -e 'N;s/\n//'
123
456

高阶用法2

暂存实践

我们可以在缓存空间和暂存空间中进行数据的简单读取,还可以对数据进行一些复杂性的编辑操作

常见的高阶命令
    P 打印模式空间开端至\n内容,并追加到默认输出之前
    h 把模式空间中的内容覆盖至暂存空间中
    H 把模式空间中的内容追加至暂存空间中
    g 从暂存空间取出数据覆盖至模式空间
    G 从暂存空间取出内容追加至模式空间
    x 把模式空间中的内容与暂存空间中的内容进行互换
# 实践1-暂存空间基本实践
seq 4 > seq.txt

# 每一行后面都有换行符号$
cat -e seq.txt
1$
2$
3$
4$

# h 把模式空间中的内容覆盖至暂存空间中 G 从暂存空间取出内容追加至模式空间
# /2/h 将匹配到的内容存储到 暂存空间;$ 正常信息输出的时候,不输出暂存空间的信息;G 代表信息操作完毕后,将暂存区的内容,追加到模式空间(我的理解:这里的$符号代表最后一行的意思???)
sed -e '/2/h' -e '$G' seq.txt
1
2
3
4
2

# 取消$,每次输出信息的时候,同时输出缓存区和暂存区的内容
sed -e '/2/h' -e 'G' seq.txt
1
					# 此时暂存区为空
2
2					# 此时暂存区内容为2
3
2					# 此时暂存区内容为2
4
2					# 此时暂存区内容为2

# numG 代表仅在num位置输出暂存区信息
sed -e '/2/h' -e '1G' seq.txt
1
				# 在第1个位置输出暂存区信息
2
3
4

# num1,num2G 代表仅在num1-num2范围的位置输出暂存区信息
sed -e '/2/h' -e '1,2G' seq.txt
1
				# 在第1个位置输出暂存区信息
2
2				# 在第2个位置输出暂存区信息
3
4

# num!G 代表在num之外的位置输出暂存区信息
sed -e '/2/h' -e '1!G' seq.txt
1
2
2
3
2
4
2

# 实践2-暂存区使用后,清理模式空间内容

# 查看文件内容
cat -e seq.txt
1$
2$
3$
4$

# 将匹配的内容转移至暂存区,然后清理模式空间
sed -e '/2/{h;d}' -e 'G' seq.txt
1
					# 此时暂存区为空
3					# 缓存区被清理,所以没有输出2
2
4
2

# 实践3-暂存区使用后,处理清理模式空间内容

# 将匹配的内容转移至暂存区,然后清理模式空间,接着将暂存区信息输出到特定的位置
# 暂存区的信息在/3/后面显示,-e '/3/{G;}' 可以简写为 -e '/3/G' 或 -e '/3/{G}'
sed -e '/2/{h;d}' -e '/3/{G;}' seq.txt
1
3
2
4

# g 从暂存空间取出数据覆盖至模式空间
# g 的作用,是将/3/匹配到的缓存区内容被暂存区的信息覆盖,缓存区内容是2
sed -e '/2/{h;d}' -e '/3/g' seq.txt
1
2					# 2位置已经被删了放到暂存区,这是3位置被暂存区覆盖后显示的
4

# x 把模式空间中的内容与暂存空间中的内容进行互换
# x 的作用,是将/3/匹配到的缓存区内容和暂存区的信息交换,则缓存区内容是2,暂存区是3
sed -e '/2/{h;d}' -e '/3/{x;G}' seq.txt
1
2				# 缓存区的3被暂存区的2替换了
3				# 暂存区的3被追加到缓存区
4

其他实践

# 实践1-内容倒序实践

# 除了第1行不输出暂存区,其他都输出暂存区值
sed -e '1!G' seq.txt
1
2
					# 第2处位置的暂存区为空
3
					# 第3处位置的暂存区为空
4
					# 第4处位置的暂存区为空

# h将所有模式空间的内容覆盖到暂存区 
sed -e '1!G;h' seq.txt
1					# 暂存区在第1处缓存区不输出
2					# 缓存空间的1覆盖暂存区,然后在当前缓存区的2之后输出--追加
1					
3					# 缓存空间的21覆盖暂存区,然后在当前缓存区的3之后输出--追加
2					
1
4					# 缓存空间的321覆盖暂存区,然后在当前缓存区的4之后输出--追加
3
2
1

# $!d 代表除了最后一个位置内容不删除,其他的都清除掉
sed -e '1!G;h;$!d' seq.txt
4
3
2
1

# 实践2-提取关键信息的前一行

# 除了第1行不被暂存区覆盖,其他缓存区被暂存区覆盖
sed -e '1!g' seq.txt
1
 					# 第2处位置被暂存区的空覆盖
					# 第3处位置被暂存区的空覆盖
					# 第4处位置被暂存区的空覆盖 

# 将匹配到的3不打印,存放到暂存区,然后仅输出缓存区信息
# /3/ 匹配到第3行的内容3,使用!p不输出,然后依次将缓存区内容覆盖暂存区
sed -n '/3/!p;h' seq.txt
1
2
4

sed -n '/3/g;p;h' seq.txt
1		# 条件不匹配,交给h动作,缓存区的1会覆盖到暂存区
2		# 条件不匹配,交给h动作,缓存区的2会覆盖到暂存区
2		# 条件匹配,暂存区的2通过g覆盖缓存区的3,所以输出2
4

# 每一行都执行h,将缓存区覆盖到暂存区;执行到第三行时,前面的条件匹配,会将暂存区的2覆盖到缓存区并打印,因此只输出2
sed -n '/3/{g;p};h' seq.txt
2

# 实践3-打印最后两行(偶数行)或一行(奇数行)信息
# 除了最后一行外,N 读取匹配到的行的下一行追加至模式空间(下一行相当于也读过了,下一次读取会接着下一行的下一行),d 删除;这样相当于每次读取两行并删除,最后会剩下两行或一行
sed '{$!N;$!d}' seq.txt
3
4

脚本实践

案例需求

案例描述:
	搭建一个ftp服务器
	
属性要求
    1 不支持本地用户登录		
        local_enable=NO
    2 匿名用户可以上传 新建 删除	 
        anon_upload_enable=YES  anon_mkdir_write_enable=YES	
    3 匿名用户限速500KBps  
        anon_max_rate=500000
# 准备工作

# 使用sed获取ip地址
# ipaddr=$(ifconfig ens33 | sed -n '2p' | sed -r 's/.*inet (.*) net.*/\1/')
ipaddr=$(ifconfig ens33 | sed -n '2p' | sed -e 's/.*inet \(.*\) net.*/\1/g')
# iptail=$(echo $ipaddr | cut -d '.' -f 4)
iptail=$(echo $ipaddr|cut -d'.' -f4)

# 修改主机名
hostname server$iptail.localhost.com
echo "HOSTNAME=server$iptail.localhost.com" >> /etc/sysconfig/network
echo "$ipaddr server$iptail.localhost.com" >> /etc/hosts

# 环境部署

# 安装软件
yum -y install vsftpd lftp

# 备份配置,备份文件为/etc/vsftpd/vsftpd.conf.default
cp /etc/vsftpd/vsftpd.conf{,.default}

# 清理无效信息
sed -rn '/^(#|$)/!p' /etc/vsftpd/vsftpd.conf
# sed -ri '/^(#|$)/d' /etc/vsftpd/vsftpd.conf
sed -ri '/^#/d;/^$/d' /etc/vsftpd/vsftpd.conf

# 禁止本地登录
# c 表示整行替换
sed -i '/local_enable/c\local_enable=NO' /etc/vsftpd/vsftpd.conf

# 允许匿名操作
sed -i '$a anon_upload_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_mkdir_write_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_other_write_enable=YES' /etc/vsftpd/vsftpd.conf
sed -i '$a anon_max_rate=512000' /etc/vsftpd/vsftpd.conf

# 启动服务
service vsftpd restart
# 数据操作

# 测试验证
chmod 777 /var/ftp/pub
cp /etc/hosts /var/ftp/pub

# 测试下载
cd /tmp
lftp $ipaddr << EOF
cd pub
get hosts
exit
EOF

# 检查下载后的文件,文件存在则匿名用户下载成功
ls /tmp/hosts

# 关闭selinux
sed -ri 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
setenforce 0

# 测试上传、创建目录、删除目录等,需要先关闭selinux,下面的操作才能成功
cd /tmp
lftp $ipaddr << EOF
cd pub
mkdir test1
mkdir test2
put /etc/group
rmdir test2
exit
EOF

tree /var/ftp/pub/
/var/ftp/pub/
├── group
├── hosts
└── test1

简单实践

脚本实践 vsftpd_install_manager.sh

#!/bin/bash
# 功能:定制vsftpd环境部署功能
# 版本:v0.1
# 作者:example
# 联系:www.example.com

# 定制普通变量
net_card="ens33"
vsftpd_conf='/etc/vsftpd/vsftpd.conf'

# 定制命令变量
ipaddr=$(ifconfig ${net_card} | sed -n '2p' | sed -e 's/.*inet \(.*\) net.*/\1/g')
iptail=$(echo $ipaddr|cut -d'.' -f4)

# 定制目标类型变量
target_type=(部署 下载 操作 卸载)

# 定制服务的操作提示功能函数
menu(){
    echo -e "\e[31m---------------管理平台登录界面---------------"
    echo -e " 1: 部署软件 2: 下载测试  3: 操作测试 4: 卸载"
    echo -e "-------------------------------------------\033[0m"
}

# 设定主机名
hostname_set(){
    hostname server$iptail.localhost.com
    echo "HOSTNAME=server$iptail.localhost.com" >> /etc/sysconfig/network
    echo "$ipaddr server$iptail.localhost.com" >> /etc/hosts
}

# 软件安装
softs_install(){
    yum -y install vsftpd lftp &>/dev/null
}

# 软件卸载
softs_uninstall(){
    service vsftpd stop &>/dev/null
    yum -y remove vsftpd lftp &>/dev/null
    rm -rf /etc/vsftpd
    rm -rf /var/ftp
    sed -i '/HOSTNAME/d' /etc/sysconfig/network
    sed -i "/${ipaddr}/d" /etc/hosts
    echo "ftp卸载成功"
}

# 修改配置
update_config(){
    # 关闭selinux
    sed -ri 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
    setenforce 0
    cp ${vsftpd_conf}{,.default}
    sed -ri '/^#/d;/^$/d' ${vsftpd_conf}
    sed -i '/local_enable/c\local_enable=NO' ${vsftpd_conf}
    sed -i '$a anon_upload_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_mkdir_write_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_other_write_enable=YES' ${vsftpd_conf}
    sed -i '$a anon_max_rate=512000' ${vsftpd_conf}
    service vsftpd restart &>/dev/null && echo "vsftpd服务启动成功"
}

# 定制部署流程
soft_deploy(){
    hostname_set
    softs_install
    update_config
}

# 匿名用户测试下载动作
download_test(){
    # 准备数据目录及配套文件
    chmod 777 /var/ftp/pub
    cp /etc/hosts /var/ftp/pub

    # 测试演示
    cd /tmp
    rm -f hosts
    lftp $ipaddr <<-EOF
    cd pub
    get hosts
    exit
    # 下面的EOF前面是制表符,不能是空格
	EOF
}

# 匿名用户测试操作权限
operator_test(){
    # 测试上传、创建目录、删除目录等
    cd /tmp
    lftp $ipaddr <<-EOF
    cd pub
    mkdir test1 test2
    put /etc/group
    rmdir test2
    exit
	EOF
}

# 定制脚本帮助信息
Usage(){
    echo "请输入有效的操作标识!!!"
}

# 定制业务逻辑
while true
do
    menu
    read -p "> 请输入要操作的目标类型: " target_id
    if [ ${target_type[$target_id-1]} == "部署" ];then
        echo "开始部署ftp..."
        soft_deploy
    elif [ ${target_type[$target_id-1]} == "下载" ];then
        download_test
        # 测试结果
        if [ -f /tmp/hosts ];then
            echo "匿名用户下载成功"
            rm -f /tmp/hosts
        else
            echo "匿名用户下载失败"
        fi
    elif [ ${target_type[$target_id-1]} == "操作" ];then
        operator_test
        if [ -d /var/ftp/pub/test1 ];then
            if [ ! -d /var/ftp/pub/test2 ];then
                if [ -f /var/ftp/pub/group ];then
                    echo "匿名操作权限正常"
                    rm -rf /var/ftp/pub/*
                fi
            fi
        fi
    elif [ ${target_type[$target_id-1]} == "卸载" ];then
        softs_uninstall
    else
        Usage
    fi
done
/bin/bash vsftpd_install_manager.sh
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试 4: 卸载
-------------------------------------------
> 请输入要操作的目标类型: 4
ftp卸载成功
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试 4: 卸载
-------------------------------------------
> 请输入要操作的目标类型: 1
开始部署ftp...
vsftpd服务启动成功
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试 4: 卸载
-------------------------------------------
> 请输入要操作的目标类型: 2
匿名用户下载成功
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试 4: 卸载
-------------------------------------------
> 请输入要操作的目标类型: 3
匿名操作权限正常
---------------管理平台登录界面---------------
 1: 部署软件 2: 下载测试  3: 操作测试 4: 卸载
-------------------------------------------
> 请输入要操作的目标类型: ^C