一、功能简介
sed(Stream EDitor),是一种流文件编辑器,其特点为:
1、非交互式。处理文件时,把当前处理的行存储在临时缓冲区(称为“模式空间”(Pattern Space))中,即它是基于模式匹配过滤及修改文本。 //类比:Vim是一个交互的编辑器
2、逐行处理,sed 命令在缓冲区中一次处理一行内容,处理完成后,把缓冲区的内容送往屏幕,接着处理下一行,直到文件末尾。
正常情况下,sed命令所做的处理只是把操作结果(包括打印、删除等)输出到当前终端屏幕,而并不会对原始文件做任何更改。若希望直接修改文件内容,应添加选项 -i 。为了避免生产环境中因误操作导致系统故障,使用时要谨慎。
sed 主要用来编辑一个或多个文件,简化对文件的反复操作(如:输出、删除、替换、复制、剪切、导入、导出等各种操作)或者用来编写转换程序等。
二、语法格式
格式1:前置命令 | sed [选项] '[定址符]处理动作’ [input-file]
格式2:sed [选项] '[定址符]处理动作’文件 [input-file]
其中:处理动作可以由-e指定多个处理动作,input-file为输入文件,可指定多个。
三、语法说明
1、选项说明
-i:直接修改文件内容,而不是输出到终端。
-n:安静模式,屏蔽默认输出(全部文本),只有经过sed特殊处理的那一行才会被列出来。
-e :指定sed动作,可以由多个-e指定多个动作。
-r:启用扩展的正则表达式,若与其他选项一起使用,应作为首个选项
-f :直接将sed的动作写在一个文件内,-f filename则可以运行filename 内的sed动作;
--help:显示帮助。
--version:显示版本。
-{}:可组合多个命令,以分号分隔
2、动作说明
[n1[,n2]] function
其中[n1[,n2]]称为定址符,用来定义需要操作的文本,如指定处理的起、止行数。
n1, n2 不见得会存在,一般代表“选择进行动作的行数”,举例来说,如果我的动作是需要在 10 到 20 行之间进行,则写作“10,20动作行为”。
地址可表示为文本的“行号”,或者用来匹配的“/正则表达式/”
省略定址符时,默认逐行处理全部文本。
fuction为编辑指令,可以为增删改查等指令
a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d :删除,因为是删除啊,所以 d 后面通常不接任何内容;
i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p :打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s :替换,通常这个s的动作可以搭配正规表示法!例如 1,20s/old/new/g。
四、典型示例
基本的处理动作
操作符 |
用途 |
指令示例 |
解释 |
p |
打印行(print) |
2,4p |
输出第2-4行 |
2p,4p |
输出第2行、第4行 |
||
d |
删除行(delete) |
2,4d |
删除第2-4行 |
s |
字符串替换(substitution) |
s/old/new/ |
将每行的第1 个old替换为new |
s/old/new/3 |
将每行的第3 个old替换为new |
||
s/old/new/g |
将所有的old都替换为new |
替换担任的分隔“/”,可改用其他字符,如#、&等,便于修改文件路径
示例:把/bin/bash替换为/sbin/sh (/etc/passwd)
# sed "s/\/bin\/bash/\/sbin\/sh/" /etc/passwd 也可写成
# sed "s#/bin/bash#/sbin/sh#" /etc/passwd
sed "s9\98\97\99\984\9\98\99g" 文件 :将98979---》9849989
在这里9是特殊符号,加\屏蔽特殊含义
sed文本块处理
操作符 |
用途 |
指令示例 |
指令解析 |
i insert |
行前插入文本 |
sed "2iYY" file |
在第2行之前添加文本行“YY” |
sed "4,7iYY" file |
在第4~7行的第一行前添加文本行“YY” |
||
a append |
行后插入文本 |
sed "2aYY" file |
在第2行之后添加文本 |
sed "/^XX/aYY" file |
在以XX开头的行之后添加文本 |
||
c change |
替换当前行 |
sed "2cYY" file |
将第2 行的内容修改为“YY” |
在匹配行后面追加字符串 | sed "/string/s/.*/&ttt/" file |
处理多行文本:修改后的文本有多行时,以换行符\n分隔,或使用\强制换行
示例:修改主机名配置文件
1)确认修改前的配置 # cat /etc/sysconfig/network
2)使用sed修改主机名配置所在行的内容(c整行替换)
# sed '/^HOSTNAME/cHOSTNAME=' /etc/sysconfig/network
示例:修改hosts文件,添加新的记录
1)确认修改前的配置 # cat /etc/hosts
2)使用sed修改hosts文件,添加两行新纪录(a追加)
# sed -i '$a192.168.4.5 svr5\
> 119.75.217.56 ' /etc/hosts
常见处理操作示例
操作 |
示例 |
含义解析 |
输出文本 |
sed '1p' 或 sed -n 'p' |
输出所有行,等同于cat
|
sed -n '1p' |
输出第1行 |
|
sed -n '4p' |
输出第4行 |
|
sed -n '$p' |
输出最后一行 |
|
sed -n '5,$p' |
从第5行输出到最后一行 |
|
sed -n '4,7p' |
输出第4~7行 |
|
sed -n '4,+10p' |
输出第4行及其后的10行内容,共11行 |
|
sed -n '2p;5p;7p' sed -n '{2p;5p;7p}' |
输出第2,5,7行 用分号来隔离多个操作(如果有定址条件,则应该使用{ }括起来) |
|
sed -n '/a/p' |
|
|
sed -n '/A/p' |
|
|
sed -n '/^id/p' |
列出以id开头的行: |
|
sed -n '/^a/p;/^r/p' |
|
|
sed -n '/local$/p' |
输出以local结尾的行 |
|
sed -n 'p;n' |
输出奇数行,n表示读入下一行文本(隔行)next |
|
sed -n 'n;p' |
输出偶数行,n表示读入下一行文本(隔行) |
|
sed -n '$=' |
输出文件的行数, wc -l返回行数及文件名 |
|
删除文本 |
sed -i 'd' |
删除所有 |
sed -i '$d' |
删除文件的最后一行 |
|
sed -i '/^$/d' |
删除所有空行 |
|
sed -i '1d' |
删除第1行 |
|
sed -i '2,5d' |
删除第2~5行 |
|
sed -i "${var1},${var2}d" filename # 这里引号必须为双引号 |
删除shell变量表示的行号(配合for等语句使用) |
|
sed -i '5d;7d;9d' |
删除第5、7、9行 |
|
sed -i '/init/d;/bin/d' |
删除所有包含“init”及“bin”的行 |
|
sed -i '/[0-9]/d' |
|
|
sed -i '/^#/d' |
|
|
sed -i '/^s/d' |
|
|
sed -i '/^s/d' |
直接删除 |
|
sed -i '/^install/d' |
删除以install开头的行 |
|
sed -i '/xml/d' |
删除所有包含xml的行,只作输出,不更改原文件,若需要更改,应添加选项-i |
|
sed -i '/xml/!d' 等效于 sed -n '/xml/p' |
删除不包含xml的行,!符号表示取反 |
|
替换文本 |
sed 's/xml/XML/' |
将每行中第1个xml替换为XML |
sed '4s/xml/XML/' | 将第4行中的xml替换为XML | |
sed 's/xml/XML/3' |
将每行中第3个xml替换为XML,只作输出,不更改原文件(若需要更改,应添加选项-i) |
|
sed '2s/xml/XML/3' |
将第2行中第3个xml替换为XML,只作输出,不更改原文件(若需要更改,应添加选项-i) |
|
sed 's/xml/XML/g' |
将所有的xml都替换为XML |
|
sed 's/xml//g' |
将所有的xml都删除(替换为空串) |
|
sed 's/doc/&s/g' |
将所有的doc都替换为docs,&代表查找串 |
|
sed -i '4,7s/^/#/' |
将第4~7行注释掉(行首加#号) |
|
sed -i '3,5s/^#//' |
解除文件第3~5行的注释(去掉开头的 # ) |
|
sed 's/^#an/an/' |
解除以#an开头的行的注释(去除行首的#号) |
|
sed 's/xml\|XML\|e//g' |
删除所有的“xml”、所有的“XML”、所有的字母e,或者的关系用转义方式 \| 来表示 |
五、综合运用
1)删除文件中每行的第二个、最后一个字符
分两次替换操作,第一次替换掉第2个字符,第二次替换掉最后一个字符:
# sed 's/.//2;s/.$//'
2)将文件中每行的第一个、第二个字符互换
每行文本拆分为“第1个字符”、“第2个字符”、“剩下的所有字符”三个部分,然后通过替换操作重排顺序为“2-1-3”:# sed -r 's/^(.)(.)(.*)/\2\1\3/'
3)将第一个字符与最后一字符对调:sed -r “s/^(.)( .*)(.)$/\3\2\1/”
将第一个字符与最后一字符对调:sed -r "s/^(.)(.)(.*)(.)(.)$/\1\4\3\2\5/"
4)删除文件中所有的数字、行首的空格
因原文件内没有数字,行首也没有空格,这里稍作做一点处理,生成一个新测试文件:
# sed 's/o/o7/;s/l/l4/;3,5s/^/ /' >
# cat
以文件为例,删除所有数字、行首空格的操作如下:
# sed -r 's/[0-9]//g;s/^( )+//'
5)为文件中每个大写字母添加括号:
# sed 's/[A-Z]/(&)/g' //使用“&”可调用s替换操作中的整个查找串
或者 # sed -r "s/([A-Z])/(\1)/g"
示例:修改默认运行级别
# sed -i '/^id:/s/3/5/' /etc/inittab //将默认运行级别修改为5
# grep "^id:" /etc/inittab //确认修改结果
示例:修改IP地址(网段):修改IP地址的网段部分,主机地址不变。
直接修改网卡eth0的配置文件,检查原有的配置内容:
# cat /etc/sysconfig/network-scripts/ifcfg-eth0
IPADDR=192.168.4.4
若希望将IP地址192.168.4.4修改为172.16.16.4,则应该定位到“IPADDR”所在的行,执行相应的替换(仅测试,尚未修改):
# sed '/^IPADDR/s/192.168.4.4/172.16.16.4/' \
/etc/sysconfig/network-scripts/ifcfg-eth0 | grep "^IPADDR"
要求只修改网段地址时,可以利用扩展正则表达式的 \1、\2、……等调用,分别对应此前第1个、第2个、…… 以 ()包围的表达式所匹配的内容。
所以上述操作可以改为如下(启用扩展匹配应添加 -r 选项):
# sed -r -i '/^IPADDR/s/192.168.4.(.*)/172.16.16.\1/' \
/etc/sysconfig/network-scripts/ifcfg-eth0
确认修改结果:# grep "^IPADDR" /etc/sysconfig/network-scripts/ifcfg-eth0
示例:调整httpd服务配置,更改网站根目录
由于需要替换的字符串中有 / ,为了避免与sed替换操作的分隔混淆,可以使用其他字符作为替换分隔,比如可改用“s#old#new#”的方式实现替换:
# sed -i 's#/var/www/html#/opt/wwwroot#' \
/etc/httpd/conf/
# grep "^DocumentRoot" /etc/httpd/conf/
DocumentRoot "/opt/wwwroot"
示例:修改/etc/hosts :sed -i ‘1a 1.1.1.1 域名’ /etc/hosts
示例:装配匿名FTP服务
通过yum安装vsftpd软件包;修改vsftpd服务配置,开启匿名上传;调整/var/ftp/pub目录权限,允许ftp写入;启动vsftpd服务,并设置开机自运行
1)任务需求及思路分析
vsftpd服务的安装、改目录权限、起服务等操作可以直接写在脚本中。
修改配置的工作可以使用sed命令,根据默认配置,只需要定位到以#anon开头的行,去掉开头的注释即可。
2)根据实现思路编写脚本文件
#!/bin/bash
yum -y install vsftpd //安装vsftpd软件
cp /etc/vsftpd/{,.bak} //备份默认的配置文件
sed -i "/^#anon/s/^#//" /etc/vsftpd/ //修改服务配置
chown ftp /var/ftp/pub //调整目录权限
/etc//vsftpd restart //启动服务
chkconfig vsftpd on //设为自动运行
# chmod +x
3)验证、测试脚本
运行脚本:
# ./
使用ftp登录服务,测试是否可以上传:
# ftp localhost //本机访问测试
……
Name (localhost:root): ftp //匿名登录
……
ftp> cd pub //切换到 pub/ 目录
……
ftp> put //上传当前目录下的 文件
……
ftp> quit //断开FTP连接
查看/var/ftp/pub新上传的文件:# ls -lh /var/ftp/pub/
示例:clone-vm7脚本
#!/bin/bash
# exit code:
# 65 -> user input nothing
# 66 -> user input is not a number
# 67 -> user input out of range
# 68 -> vm disk image exists
IMG_DIR=/var/lib/libvirt/images
BASEVM=rh7_template
ROOM=`sed -n "1p" /etc/hostname | sed -r 's/(room)([0-9]{1,})(.*)/\2/'`
if [ $ROOM -le 9 ];then
ROOM=0$ROOM
fi
IP=`sed -n "1p" /etc/hostname | sed -r 's/(.*)([0-9]+)(.*)/\2/'`
read -p "Enter VM number: " VMNUM
if [ $VMNUM -le 9 ];then
VMNUM=0$VMNUM
fi
if [ -z "${VMNUM}" ]; then
echo "You must input a number."
exit 65
elif [ $(echo ${VMNUM}*1 | bc) = 0 ]; then
echo "You must input a number."
exit 66
elif [ ${VMNUM} -lt 1 -o ${VMNUM} -gt 99 ]; then
echo "Input out of range"
exit 67
fi
NEWVM=rh7_node${VMNUM}
if [ -e $IMG_DIR/${NEWVM}.img ]; then
echo "File exists."
exit 68
fi
echo -en "Creating Virtual Machine disk image......\t"
qemu-img create -f qcow2 -b $IMG_DIR/.${BASEVM}.img $IMG_DIR/${NEWVM}.img &> /dev/null
echo -e "\e[32;1m[OK]\e[0m"
#virsh dumpxml ${BASEVM} > /tmp/
cat /var/lib/libvirt/images/. > /tmp/
sed -i "/<name>${BASEVM}/s/${BASEVM}/${NEWVM}/" /tmp/
sed -i "/uuid/s/<uuid>.*<\/uuid>/<uuid>$(uuidgen)<\/uuid>/" /tmp/
sed -i "/${BASEVM}\.img/s/${BASEVM}/${NEWVM}/" /tmp/
sed -i "/mac /s/a1/${ROOM}/" /tmp/
sed -i "/mac /s/a2/${IP}/" /tmp/
sed -i "/mac /s/a3/${VMNUM}/" /tmp/
sed -i "/mac /s/b1/${ROOM}/" /tmp/
sed -i "/mac /s/b2/${IP}/" /tmp/
sed -i "/mac /s/b3/${VMNUM}/" /tmp/
sed -i "/mac /s/c1/${ROOM}/" /tmp/
sed -i "/mac /s/c2/${IP}/" /tmp/
sed -i "/mac /s/c3/${VMNUM}/" /tmp/
sed -i "/mac /s/d1/${ROOM}/" /tmp/
sed -i "/mac /s/d2/${IP}/" /tmp/
sed -i "/mac /s/d3/${VMNUM}/" /tmp/
echo -en "Defining new virtual machine......\t\t"
virsh define /tmp/ &> /dev/null
echo -e "\e[32;1m[OK]\e[0m"
文件导入导出操作
基本动作:r动作应结合-i选项才会存入,否则只输出
W动作以覆盖的方式另存为新文件
操作符 |
用途 |
指令示例 |
指令解析 |
r |
读取文件 |
3r |
在第3行下方插入文件 |
4,7r |
在第4~7每一行后插入文件 |
||
w |
保存到文件 |
3w |
将第3行另存为文件 |
4,7w |
将第4~7行另存为文件 |
sed -n ‘/^XX/w ’ 与sed -n ‘/^XX/p ’ > 操作效果相同
sed复制剪切
模式空间:存放当前处理的行,将处理结果输出
若当前行不符合处理条件,则原样输出
处理完当前行再读入下一行来处理
保持空间:作用类似于“剪贴板”
默认存放一个空行(换行符\n)
基本动作:
复制:H:模式空间---[追加]--->保持空间
h:模式空间---[覆盖]--->保持空间
粘贴:G:保持空间---[追加]--->模式空间
g:保持空间---[覆盖]--->模式空间
示例:把第1-3行复制到文件末尾:sed ‘1,3H;$G’
改用’1h;2,3H;$G’可避免出现空行 (出现空行的原因是保持空间内默认已有一个空行)
示例:把第2行复制第4行:# sed '2H;4G' 追加出现空行
# sed '2h;4G' 不要空行
示例:把第1行剪切到文件末尾:sed ‘1h,1d;$G’
把第1-2行剪切到文件末尾:sed ‘1h;2H;1,2d;$G’
sed流控制
!取反操作 ——根据定址条件取反
示例:把/etc/passwd中能登录的用户及其密码(/etc/shadow)提取出来
找到使用bash作登录Shell的本地用户;列出这些用户的shadow密码记录
按每行“用户名 --> 密码记录”保存到
基本思路如下:
1.先用sed工具取出登录Shell为/bin/bash的用户记录,保存为临时文件/tmp/,并计算记录数量
2.再结合while循环遍历取得的账号记录,逐行进行处理
3.针对每一行用户记录,采用掐头去尾的方式获得用户名、密码字串
4.按照指定格式追加到/tmp/文件
5.结束循环后删除临时文件,报告分析结果
示例:列出不使用bash的用户帐号记录:
# sed -n '/bash$/!p' /etc/passwd
# sed -n '/bash$/s/:.*//p' /etc/passwd
# sed -n '/:\/bin\/bash$/w /tmp/' /etc/passwd
方法一:
#/bin/bash
> /tmp/ ## 创建空文件
sed -n '/:\/bin\/bash$/w /tmp/' /etc/passwd ## 提取符合条件的账号记录
UNUM=$(egrep -c '.' /tmp/) ## 取得记录个数
while [ ${i:=1} -le $UNUM ] ## 从第1行开始,遍历账号记录
do
UREC=$(sed -n "${i}p" /tmp/) ## 取指定行数的记录
NAME=${UREC%%:*} ## 截取用户名(记录去尾)
PREC=$(sed -n "/^$NAME:/p" /etc/shadow) ## 查找与用户名对应的密码记录
PASS=${PREC#*:} ## 掐头
PASS=${PASS%%:*} ## 去尾,只留下密码记录
echo "$NAME --> $PASS" >> /tmp/ ## 保存结果
let i++ ## 自增1,转下一次循环
done
/bin/rm -rf /tmp/ ## 删除临时文件
echo "用户分析完毕,请查阅文件 /tmp/" ## 完成后提示
# chmod +x ./
步骤二:测试、验证执行结果 # ./
用户分析完毕,请查阅文件 # less /tmp/
方法二:
#/bin/bash
NAME=`sed -n "/bash$/s/:.*//p" /etc/passwd` ## 截取用户名(记录去尾)
for i in $NAME
do
pass1=`grep $i /etc/shadow` ## 查找与用户名对应的密码记录
pass2=${pass1#*:} ## 掐头
PASS=${pass2%%:*} ## 去尾,只留下密码记录
echo "$i ---> $PASS" >>/tmp/
done
echo "用户分析完毕,请查阅文件 /tmp/" ## 完成后提示
从上述参考脚本可以发现,使用sed来实现字段提取会比较复杂。下一章课程将会学到awk命令,届时可以通过更简单的方法来改进此脚本内容。
示例:将日志按最后的时间降序排列
练习用数据,先创建一个文本,将数据复制进去:
2018-12-24T02:00:45.586+0800 I COMMAND [conn24809] command command: find { find: "CollDevice", filter: { pn: { $in: [ "B0016480016385", "B0716471013985", "B0716471023384", "B0716471030050", "B0716471049514", "B0716471059334", "B0716471069304", "B0716471078243", "B0716471080711" ] protocol:op_query 4613ms
2018-12-24T02:00:45.584+0800 I COMMAND [conn19808] command command: find { find: "CollUsr", filter: { uid: 38882 protocol:op_query 4612ms
2018-12-24T02:00:45.584+0800 I COMMAND [conn19771] command command: find { find: "CollDevice", filter: { pn: "H4918090264369", devcode: 742, devaddr: 1, sn: "102108771809038", : 9201, dts: { $exists: false protocol:op_query 4580ms
2018-12-24T02:00:45.585+0800 I COMMAND [conn24811] command command: find { find: "CollPlant", filter: { pid: 41532 protocol:op_query 4549ms
2018-12-24T02:00:45.585+0800 I COMMAND [conn19825] command command: find { find: "CollPlant", filter: { pid: 42225 protocol:op_query 4540ms
2018-12-24T02:00:45.584+0800 I COMMAND [conn19773] command command: find { find: "CollPlant", filter: { pid: 45122 protocol:op_query 4537ms
2018-12-24T02:00:45.584+0800 I COMMAND [conn8] command command: find { find: "CollTempFile", filter: { w: true protocol:op_query 4519ms
2018-12-24T02:00:45.585+0800 I COMMAND [conn19801] command command: find { find: "CollPlant", filter: { uid: 38882 protocol:op_query 4164ms
命令:sed -r 's/(.*) ([0-9]*ms$)/\2 \1/' | sort -nr | sed -r 's/([0-9]*ms) (.*$)/\2 \1/'
sed -i '1i添加的内容' filename # 在第一行前添加字符串
sed -i '$i添加的内容' filename # 在最后一行行前添加字符串
sed -i '$a添加的内容' filename # 在最后一行行后添加字符串