且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

《构建高可用Linux服务器 第3版》—— 3.2 Sed的基础用法及实用举例

更新时间:2022-10-06 16:56:10

本节书摘来自华章出版社《构建高可用Linux服务器 第3版》一 书中的第3章,第3.2节,作者:余洪春 ,更多章节内容可以访问云栖社区“华章计算机”公众号查看。

3.2 Sed的基础用法及实用举例

Sed是Linux/UNIX平台下的轻量级流编辑器,日常一般用于处理文本文件。Sed有许多很好的特性。首先,它相当小巧;其次,Sed可以配合强大的Shell来完成许多复杂的功能。在我看来,Sed完全可以被看成是一个脚本解释器,它用类似于编程的手段完成许多事情。我们完全可以用Vim+Sed的方式来处理日常工作中的大多数文档。

3.2.1 Sed的基础语法格式

Sed的格式如下所示:

sed [-nefr] [n1,n2] action
其中:

-n:是安静模式,只有经过Sed处理的行才显示出来,其他不显示。

-e:表示直接在命令行模式上进行Sed的操作,是默认选项,不用写。

-f:将Sed的操作写在一个文件里,用的时候“-f filename”就可以按照内容进行Sed操作了。

-r:表示使Sed支持扩展正则表达式。

n1,n2:不一定需要,选择要进行处理的行。如10,20表示在10~20行之间处理。

Sed的action(动作)支持如下参数。

a:表示添加,后接字符串,添加到当前行的下一行。

c:表示替换,后接字符串,用它替换n1到n2之间的行。

d:表示删除符合模式的行,它的语法为sed '/regexp/d',斜杠之间是正则表达式,模式在d前面,d后面一般不接任何内容。

i:表示插入,后接字符串,添加到当前行的上一行。

p:表示打印,打印某个选择的数据,通常与-n安静模式一起使用。

s:表示搜索,还可以替换,类似于Vim里的搜索替换功能。例如:1,20s/old/new/g表示替换1~20行的old为new,g在这里表示处理这一行所有匹配的内容。

注意 动作***用''括起来,防止因空格导致错误。

Sed实例如下所示(下面所有实例在CentOS 5.8_x64下已通过):

1)显示passwd内容,将2~5行删除后显示。

cat -n /etc/passwd |sed '2,5d'

   1  root:x:0:0:root:/root:/bin/bash

   6  games:x:5:60:games:/usr/games:/bin/sh

   7  man:x:6:12:man:/var/cache/man:/bin/sh

   8  lp:x:7:7:lp:/var/spool/lpd:/bin/sh

...
2)在第2行后面的一行加上“Hello China!”字符串。

cat -n /etc/passwd |sed '2a Hello China!'

   1  root:x:0:0:root:/root:/bin/bash

   2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh

Hello China!

...
3)在第2行后面一行加上两行字,例如:“this is first line!”和“this is second line!”。

cat -n /etc/passwd |sed '2a This is first line! \   //使用续航符\后按回车输入后续行

This is second line!'// 以' 结尾再按回车键结束

  1  root:x:0:0:root:/root:/bin/bash

  2  daemon:x:1:1:daemon:/usr/sbin:/bin/sh

This is first line!

This is second line!

3  bin:x:2:2:bin:/bin:/bin/sh
4)将第2~5行的内容替换成“我是大好人!”。

cat -n /etc/passwd | sed '2,5c 我是大好人!'

   1  root:x:0:0:root:/root:/bin/bash

我是大好人!

   6  games:x:5:60:games:/usr/games:/bin/sh

   7  man:x:6:12:man:/var/cache/man:/bin/sh

   8  lp:x:7:7:lp:/var/spool/lpd:/bin/sh
5)只显示第5~7行,注意p与-n的配合使用!

cat -n /etc/passwd |sed -n '5,7p'

   5  sync:x:4:65534:sync:/bin:/bin/sync

   6  games:x:5:60:games:/usr/games:/bin/sh

   7  man:x:6:12:man:/var/cache/man:/bin/sh
6)使用ifconfig列出IP,我们只想要eth0的IP地址。可以先用grep取出有IP的那一行,然后用Sed去掉(替换成空)IP前面和后面的内容,如下所示:

#ifconfig eth0

eth0   Link encap:Ethernet HWaddr 00:16:36:02:41:aa 

     inet addr:172.30.171.35 Bcast:172.30.171.255 Mask:255.255.255.0

     UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

     RX packets:1221198 errors:0 dropped:0 overruns:0 frame:0

     TX packets:1125085 errors:0 dropped:0 overruns:0 carrier:0

     collisions:0 txqueuelen:1000 

     RX bytes:1477365271 (1.3 GB) TX bytes:141539593 (134.9 MB)

     Interrupt:20 

ifconfig eth0 | grep 'inet addr' |sed 's/^.*addr://g' | sed 's/Bcast.*$//g'

172.30.171.35 ```
'^.*addr:'表示从开头到addr:的字符串,将它替换为空,'Bcast.*$'表示从Bcast到结尾的串,也将它替换为空,然后就只剩下IP了。

另外一种更简便的方法如下:


ifconfig eth0 | grep "inet addr:"|awk -F[:" "]+ '{print $4}'

192.168.21.41
awk -F[:" "]的意思就是以:或空格符作为分隔符,然后打印出第4列。这里有些朋友会有疑惑,问那为什么不直接以如下方法获取IP呢?


ifconfig eth0 | grep "inet addr:" | awk -F: '{print $2}'
得出的结果如下:


192.168.21.41 Bcast
所以还需要进行一步,如下:


ifconfig eth0 | grep "inet addr:" | awk -F: '{print $2}' | awk '{print $1}'
我希望大家以这个例子好好总结一下Sed的经典用法。下面的方法其实是Awk的方法,它也是一种优秀的编辑器,现多用于对文本字段的列的截取。

7)在/etc/manpath.config中,将有MAN的设置取出,但不要说明内容。代码如下所示:


cat /etc/manpath.config |grep 'MAN'|sed 's/#.*$//g'|sed '/^$/d'

MANDATORY_MANPATH      /usr/man

MANDATORY_MANPATH      /usr/share/man

MANDATORY_MANPATH      /usr/local/man

MANPATH_MAP  /bin      /usr/share/man

MANPATH_MAP  /usr/bin    /usr/share/man

MANPATH_MAP  /sbin      /usr/share/man

MANPATH_MAP  /usr/sbin    /usr/share/man

...
注意,#不一定出现在行首。因此,/#.*$/表示#和后面的数据(直到行尾)是一行注释,将它们替换成空。/^$/表示空行,后接d表示删除空行。注意,删除空行不能用替换方法,因为空行替换成空后,那一行中还是有换行符的。

以上就是Sed的几种常见的语法命令,希望大家结合下面的实例,多在自己的机器上演示,尽快熟练掌握其用法。

####3.2.2 Sed的用法举例说明

1.Sed的基础用法

1)删除行首空格,代码如下:


sed 's/^[ ]*//g' filename

sed 's/^ *//g' filename

sed 's/^[[:space:]]*//g' filename
2)在行后和行前添加新行。

行后的命令如下:


sed 's/pattern/&\ n/g' filename
行前的命令如下:


sed 's/pattern/\ n&/g' filename
其中,&代表pattern。

3)使用变量替换(使用双引号),代码如下:


sed -e "s/$var1/$var2/g" filename
4)在第一行前插入文本,代码如下:


sed -i '1 i\ 插入字符串' filename
5)在最后一行插入,代码如下:


sed -i '$ a\ 插入字符串' filename
6)在匹配行前插入,代码如下:


sed -i '/pattern/ i "插入字符串"' filename
7)在匹配行后插入,代码如下:


sed -i '/pattern/ a "插入字符串"' filename
8)删除文本中空行和空格组成的行及#号注释的行,代码如下:


grep -v ^# filename | sed /^[[:space:]]*$/d | sed /^$/d
9)要将目录/modules下面所有文件中的zhangsan都修改成list,可用如下命令(注意备份原文件),代码如下:


sed -i 's/zhangsan/list/g' 'grep zhangsan -rl /modules'
2.巧用Vim+Sed整理nginxd.sh脚本文件

我在工作中遇到了问题,于是到Google搜索后下载了Nginx配置脚本,在复制粘贴到服务器中运行时,发现前面的001~100行都有行标识符,外带空格,影响运行和美观。本来想一行行地删除,后来觉得过于麻烦,于是想到可以用Sed来解决问题,解决方法如下。

1)在Vim里删除所有行的首数字,命令如下:


:%s/^[0-9][0-9]* // 
2)再删除所有行的首空字符,如下所示:


sed -i 's/^[[:space:]]*//' nginxd.sh
整个nginxd.sh演示脚本如下,有兴趣的朋友也可以拿来练练手。


001  #!/bin/sh

002   

003  # source function library

004  . /etc/rc.d/init.d/functions

005   

006  # Source networking configuration.

007  . /etc/sysconfig/network

008   

009  # Check that networking is up.

010  [${NETWORKING} = "no" ] && exit 0

011   

012  RETVAL=0

013  prog="nginx"

014   

015  nginxDir=/usr/local/nginx

016  nginxd=$nginxDir/sbin/nginx

017  nginxConf=$nginxDir/conf/nginx.conf

018  nginxPid=$nginxDir/nginx.pid

019   

020  nginx_check()

021  {

022    if [[ -e $nginxPid ]]; then

023      ps aux |grep -v grep |grep -q nginx

024      if(( $? == 0 )); then

025       echo "$prog already running..."

026       exit 1

027      else

028       rm -rf $nginxPid &> /dev/null

029      fi

030    fi

031  }

032   

033  start()

034  {

035    nginx_check

036    if (( $? != 0 )); then

037     true

038    else

039     echo -n $"Starting $prog:"

040     daemon $nginxd -c $nginxConf

041     RETVAL=$?

042     echo

043     [$RETVAL = 0 ] && touch /var/lock/subsys/nginx

044     return $RETVAL

045    fi

046  }

047   

048  stop()

049  {

050   echo -n $"Stopping $prog:"

051   killproc $nginxd

052   RETVAL=$?

053   echo

054   [$RETVAL = 0 ] && rm -f /var/lock/subsys/nginx $nginxPid

055  }

056   

057  reload()

058  {

059   echo -n $"Reloading $prog:"

060   killproc $nginxd -HUP

061   RETVAL=$?

062   echo

063  }

064   

065  monitor()

066  {

067   status $prog &> /dev/null

068   if (( $? == 0 )); then

069     RETVAL=0

070   else

071     RETVAL=7

072    fi

073  }

074   

075  case "$1" in

076     start)

077          start

078          ;;

079     stop)

080          stop

081          ;;

082     restart)

083          stop

084          start

085          ;;

086     reload)

087          reload

088          ;;

089     status)

090          status $prog

091          RETVAL=$?

092          ;;

093     monitor)

094          monitor

095          ;;

096     *)

097          echo $"Usage: $0 {start|stop|restart|reload|status|monitor}"

098          RETVAL=1

099  esac

100  exit $RETVA

此文件还有很多变化,比如空格在开头,序列号在中间,这也可以用Sed来解决,只是这时又应该写出怎样的Sed命令呢?这就留给大家思考吧!

3.Sed结合正则表达式批量修改文件名



注意 Sed是完全支持正则表达式的,在正则表达式里,[^.]表示为非.的所有字符,换成[^/]也可。另外,@是Sed的分隔符,我们也可以用其他符号,比如/,但是如果要用到/的话就得\/了,所以经常用的是@。



4.在配置.conf文件时,经常要为相邻的几行添加#号以注释掉

例如,我们要将test.txt文件中的第31~36行加上#号,这该如何实现呢?

在Vim中,我们可以执行如下代码:


:31,36 s/^/#/
而用Sed的话则执行起来更方便,如下所示:


sed -i '31,36s/^/#/' test.txt
反之,如果要将第31~36行带#号的全删除,用Sed该如何实现呢?方法如下:


sed –i '31,36s/^#//' test.txt
许多人习惯在这个方法后面带个g,事实上,如果没有g,则表示从行的左端开始匹配,每一行第一个与之匹配的会被换掉;如果有g,则表示每一行所有与之匹配的都会被换掉。

5.利用Sed很方便地分析日志

利用Sed还可以很方便地分析日志。例如,在以下的secure日志文件中,我想用Sed抓取12∶48∶48至12∶48∶55的日志。


Apr 17 05:01:20 localhost sshd[16375]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226 user=root

Apr 17 05:01:22 localhost sshd[16375]: Failed password for root from 222.186.37.226 port 60700 ssh2

Apr 17 05:01:22 localhost sshd[16376]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:22 localhost sshd[16377]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226 user=root

Apr 17 05:01:24 localhost sshd[16377]: Failed password for root from 222.186.37.226 port 60933 ssh2

Apr 17 05:01:24 localhost sshd[16378]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:24 localhost sshd[16379]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226 user=root

Apr 17 05:01:26 localhost sshd[16379]: Failed password for root from 222.186.37.226 port 32944 ssh2

Apr 17 05:01:26 localhost sshd[16380]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:27 localhost sshd[16381]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226 user=root

Apr 17 05:01:29 localhost sshd[16381]: Failed password for root from 222.186.37.226 port 33174 ssh2

Apr 17 05:01:29 localhost sshd[16382]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:29 localhost sshd[16383]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226 user=root

Apr 17 05:01:31 localhost sshd[16383]: Failed password for root from 222.186.37.226 port 33474 ssh2

Apr 17 05:01:31 localhost sshd[16384]: Received disconnect from 222.186.37.226: 11: Bye Bye

Apr 17 05:01:32 localhost sshd[16385]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=222.186.37.226 user=root

我们可以用tail看到下面的日志最终时间是05∶01∶32,那么如何用Sed抓取呢?利用Sed成功截取日志命令,如下所示:


cat /var/log/secure | sed -n '/12:48:48/,/12:48:55/p'

Apr 23 12:48:48 localhost sshd[20570]: Accepted password for root from 220.249.72.138 port 27177 ssh2

Apr 23 12:48:48 localhost sshd[20570]: pam_unix(sshd:session): session opened for user root by (uid=0)

Apr 23 12:48:55 localhost sshd[20601]: Accepted password for root from 220.249.72.138 port 59754 ssh2