Linux【2】-管理文件-4-行的处理(sed)

sed 是 stream editor 的缩写,中文称之为“流编辑器”。

sed 命令是一个面向行处理的工具,它以“行”为处理单位,针对每一行进行处理,处理后的结果会输出到标准输出(STDOUT)。你会发现 sed 命令是很懂礼貌的一个命令,它不会对读取的文件做任何贸然的修改,而是将内容都输出到标准输出中。

我们知道,Vim 采用的是交互式文本编辑模式,你可以用键盘命令来交互性地插入、删除或替换数据中的文本。但本节要讲的 sed 命令不同,它采用的是流编辑模式,最明显的特点是,在 sed 处理数据之前,需要预先提供一组规则,sed 会按照此规则来编辑数据。

sed 会根据脚本命令来处理文本文件中的数据,这些命令要么从命令行中输入,要么存储在一个文本文件中,此命令执行数据的顺序如下:

  1. sed 命令是面向“行”进行处理的,每一次处理一行内容。
  2. 处理时,sed 会把要处理的行存储在缓冲区中,接着用 sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。
  3. 接着处理下一行,这样不断重复,直到文件末尾。这个缓冲区被称为“模式空间”(pattern space)。

sed 本身也是一个管线命令,可以分析 standard input 的啦! 而且 sed 还可以将数据进行取代、删除、新增、撷取特定行等等的功能呢!

一、基本参数

[root@localhost ~]# sed command file

选项与参数:

-n :使用安静(silent)模式。默认情况下,sed 会在所有的脚本指定执行完毕后,会自动输出处理后的内容,而该选项会屏蔽启动输出,需使用 print 命令来完成输出。
 -e :该选项会将其后跟的脚本命令添加到已有的命令中。
 -f :直接将 sed 的动作写在一个档案内, -f filename 则可以执行 filename 内的
 sed 动作;
 -r :sed 的动作支持的是延伸型正规表示法的语法。(预设是基础正规表示法语法)
 -i :直接修改读取的档案内容,而不是由屏幕输出。

command 部分可以分为两块知识:一块是范围设定,一块是动作处理。

范围设定,可以采用两种不同的方式来表达:

  • 指定行数:比如‘3,5’表示第 3、第 4 和第 5行;而‘5,$’表示第 5 行至文件最后一行。
  • 模式匹配:比如/^[^dD]/表示匹配行首不是以 d 或 D 开头的

而动作处理部分,会提供很丰富的动作供你选择:

a :新增, a 的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)~
c :取代, c 的后面可以接字符串,这些字符串可以取代 n1,n2 之间的行!
d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i :插入, i 的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行);
p :打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运作~
s :取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦!

二、sed脚本命令

2.1 sed s 替换脚本命令

2.1.1 基本说明

我们尝试将所有以 d 或 D 开头的行里的所有小写 x 字符变为大写 X 字符:

[roc@roclinux ~]$ sed '/^[dD]/s/x/X/g' test

这个用法值得一讲,我们在 command 部分采用了 /AA/s/BB/CC/g 的语法格式,这表示我们要匹配到文件中带有 AA 的行,并且将这些行中所有的 BB 替换成 CC。

此命令的基本格式为:

[address]s/pattern/replacement/flags

其中,address 表示指定要操作的具体行,pattern 指的是需要替换的内容,replacement 指的是要替换的新内容。 关于指定具体操作行(address)的用法,这里先不做解释,文章后续会对其做详细介绍。

表 2 sed s命令flags标记及功能

flags标记 功能
n 1~512 之间的数字,表示指定要替换的字符串出现第几次时才进行替换,例如,一行中有 3 个 A,但用户只想替换第二个 A,这是就用到这个标记;
g 对数据中所有匹配到的内容进行替换,如果没有 g,则只会在第一次匹配成功时做替换操作。例如,一行数据中有 3 个 A,则只会替换第一个 A;
p 会打印与替换命令中指定的模式匹配的行。此标记通常与 -n 选项一起使用。
w file 将缓冲区中的内容写到指定的 file 文件中;
& 用正则表达式匹配的内容进行替换;
\n 匹配第 n 个子串,该子串之前在 pattern 中用 () 指定。
\ 转义(转义替换部分包含:&、\ 等)。

比如,可以指定 sed 用新文本替换第几处模式匹配的地方:

[root@localhost ~]# sed 's/test/trial/2' data4.txt
This is a test of the trial script.
This is the second test of the trial script.

可以看到,使用数字 2 作为标记的结果就是,sed 编辑器只替换每行中第 2 次出现的匹配模式。

如果要用新文件替换所有匹配的字符串,可以使用 g 标记:

[root@localhost ~]# sed 's/test/trial/g' data4.txt
This is a trial of the trial script.
This is the second trial of the trial script.

sed ’s/要被取代的字符串/新的字符串/g' 不加这个g的话,就替换一个。

我们知道,-n 选项会禁止 sed 输出,但 p 标记会输出修改过的行,将二者匹配使用的效果就是只输出被替换命令修改过的行,例如:

[root@localhost ~]# cat data5.txt
This is a test line.
This is a different line.

[root@localhost ~]# sed -n 's/test/trial/p' data5.txt
This is a trial line.

w 标记会将匹配后的结果保存到指定文件中,比如:

[root@localhost ~]# sed 's/test/trial/w test.txt' data5.txt
This is a trial line.
This is a different line.
[root@localhost ~]#cat test.txt
This is a trial line.

在使用 s 脚本命令时,替换类似文件路径的字符串会比较麻烦,需要将路径中的正斜线进行转义,例如:

[root@localhost ~]# sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd

2.1.2 更多例子

1.请把每个单词的第一个字母替换成大写。
[pp@hr-node37 linux_bin]$ echo "this is a dog" |sed 's/\b[a-z]/\u&/g'
This Is A Dog
[pp@hr-node37 linux_bin]$ echo "this is a dog" |sed 's/\b[a-z]/\u/g'
his s  og

\b大家应该知道是锚定的意思,说白了就是边界符,那么这就只会匹配第一个开头的字母,然后\U的意思在元字符里的解释是“大写(不是标题首字符)\E 以前的字符”,而\u只是将下一个字符变为大写,注意它们的区别噢。

2. 把URL中的大写字符替换成小写。

http://www.a.com/aaafkslafjlxcv/fsfa/8/Xxxx.XxXX

sed 's/[A-Z]/\l&/g' file

[解析]

同理\L的意思是使之变为小写。

#全文大小写转换

tr a-z A-Z
tr A-Z a-z

#大小写对换

echo "aBcDE" | tr '[a-zA-Z]' '[A-Za-z]
3. 更名

比如说:a.txt b.txt c.txt

更名变成 A.txt B.txt C.txt

ls *.txt|sed -nr 's/(.)(\..*)/mv & \u\1\2/e'

[解析]

\u 是转换后面的内容第一个字母为大写,\U是全部为大写直到遇到 \E 为止。这就是区别:

echo 'abc'|sed 's/^../\u&/'
Abc
echo 'abc'|sed 's/^../\U&\E/'
ABc
4. 例子

骤一:先观察原始讯息,利用 /sbin/ifconfig 查询 IP 为何?

[root@www ~]# /sbin/ifconfig eth0
 eth0 Link encap:Ethernet HWaddr 00:90:CC:A6:34:84
 inet addr:192.168.1.100 Bcast:192.168.1.255 Mask:255.255.255.0
 inet6 addr: fe80::290:ccff:fea6:3484/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
 .....(以下省略).....

#因为我们还没有讲到 IP ,这里你先有个概念即可啊!我们的重点在第二行, #也就是 192.168.1.100 那一行而已!先利用关键词捉出那一行!

步骤二:利用关键词配合 grep 撷取出关键的一行数据

[root@www ~]# /sbin/ifconfig eth0 | grep 'inet addr'
 inet addr:192.168.1.100 Bcast:192.168.1.255 Mask:255.255.255.0
 # 当场仅剩下一行!接下来,我们要将开始到 addr: 通通删除,就是像底下这样:
 # inet addr:192.168.1.100 Bcast:192.168.1.255 Mask:255.255.255.0
 # 上面的删除关键在于『 ^.*inet addr: 』啦!正规表示法出现! ^_^

步骤三:将 IP 前面的部分予以删除

[root@www ~]# /sbin/ifconfig eth0 | grep 'inet addr' |  sed 's/^.*addr://g'

 192.168.1.100 Bcast:192.168.1.255 Mask:255.255.255.0
 # 仔细与上个步骤比较一下,前面的部分不见了!接下来则是删除后续的部分,亦即:
 # 192.168.1.100 Bcast:192.168.1.255 Mask:255.255.255.0
 # 此时所需的正规表示法为:『 Bcast.*$ 』就是啦!

步骤四:将 IP 后面的部分予以删除

[root@www ~]# /sbin/ifconfig eth0 | grep 'inet addr' | sed 's/^.*addr://g' | sed 's/Bcast.*$//g' # ^代表行首,$这里也可以代表行尾
 192.168.1.100

透过这个范例的练习也建议您依据此一步骤来研究你的指令!就是先观察,然后再一层一层的试做, 如果有做不对的地方,就先予以修改,改完之后测试,成功后再往下继续测试。

5. 例子

让我们再来继续研究 sed 与正规表示法的配合练习!假设我只要 MAN 存在的那几行数据, 但是含有 # 在内的批注我不想要,而且空白行我也不要!此时该如何处理呢?可以透过这几个步骤来实作看看:

步骤一:先使用 grep 将关键词 MAN 所在行取出来

[root@www ~]# cat /etc/man.config | grep 'MAN'
# when MANPATH contains an empty substring), to find out where the cat
 # MANBIN pathname
 # MANPATH manpath_element [corresponding_catdir]
 # MANPATH_MAP path_element manpath_element
 # MANBIN /usr/local/bin/man
 # Every automatically generated MANPATH includes these fields
 MANPATH /usr/man
 ....(后面省略)....

步骤二:删除掉批注之后的数据!

[root@www ~]# cat /etc/man.config | grep 'MAN'| sed 's/#.*$//g'

MANPATH /usr/man
 ....(后面省略)....
 # 从上面可以看出来,原本批注的数据都变成空白行啦!所以,接下来要删除掉空白行

[root@www ~]# cat /etc/man.config | grep 'MAN'| sed 's/#.*$//g' | \
 > sed '/^$/d'

 MANPATH /usr/man
 MANPATH /usr/share/man
 MANPATH /usr/local/man
 ....(后面省略)....
 直接修改档案内容(危险动作)
 你以为 sed 只有这样的能耐吗?那可不! sed 甚至可以直接修改档案的内容呢!而不必使用管线命令或数据流重导向! 不过,由于这个动作会直接修改到原始的档案,所以请你千万不要随便拿系统配置文件来测试喔! 我们还是使用你下载的 regular_express.txt 档案来测试看看吧!
6. 例子

利用 sed 将 regular_express.txt 内每一行结尾若为 . 则换成 !

[root@www ~]# sed -i 's/\.$/\!/g' regular_express.txt
 # 上头的 -i 选项可以让你的 sed 直接去修改后面接的档案内容而不是由屏幕输出喔!
 # 这个范例是用在取代!请您自行 cat 该档案去查阅结果啰!
7. 例子

在windows中,文本是带有\n\t,而Linux系统带的是\n这两者是有区别的。要把文件转换一下,有两种方法:

1.命令dos2unix test.file
2.去掉"\r" ,用命令sed -i 's/\r//' test.file

我们要删除每行最后的两个字符:

#点号表示一个单个字符, 两个点号就表示两个单个字符
[roc@roclinux ~]$ sed 's/..$//' test

有人可能会问,用 sed‘/..$/d’test 为什么不行呢,d 不是表示删除么?用 d 是不行的,这是因为 d 表示删除整行内容,而非字符。'/..$/d’表示的是匹配所有末尾含有两个字符的行,然后删除这一整行内容,显然这和我们的初衷是相悖的。

第四个例子,我们希望删除每一行的前两个字符:

[roc@roclinux ~]$ sed 's/..//' test

& 符号的妙用

我们仍然通过一个场景来讲解这个知识点。

#按照惯例, 先展示文件的内容
[roc@roclinux ~]$ cat mysed.txt
Beijing
London
 
#我们使用到了&符号, 大家试着猜一猜它的作用
[roc@roclinux ~]$ sed 's/B.*/&2008/' mysed.txt
Beijing2008
London

不卖关子,答案揭晓啦,这个命令的作用是将包含‘B.*’的字符串后面加上 2008 四个字符。这个命令中我们用到了 & 字符,在 sed 命令中,它表示的是“之前被匹配的部分”,在我们的例子中当然就是 Beijing 啦!

我们再通过一个例子强化一下大家对&符号的理解:

#这个例子或许更易理解
[roc@roclinux 20160229]$ sed 's/Bei/&2008/' mysed.txt
Bei2008jing
London

sed 中的括号有深意

在 sed 命令中,其实小括号‘()’也是有深意的。我们开门见山,通过一个例子,让大家见识一下小括号的威力:

[roc@roclinux ~]$ echo "hello world" | sed 's/\(hello\).*/world \1/'
world hello

我们看到,原本是“hello world”,经过 sed 的处理,输出变成了“world hello”。

这个例子中就用到了小括号的知识,我们称之为“sed 的预存储技术”,也就是命令中被“(”和“)”括起来的内容会被依次暂存起来,存储到 \1、\2…里面。这样你就可以使用‘\N’形式来调用这些预存储的内容了。

来继续看一个例子,我们希望只在每行的第一个和最后一个 Beijing 后面加上 2008 字符串,言下之意就是,除了每行的第一个和最后一个 2008 之外,这一行中间出现的 Beijing 后面就不要加 2008 啦。这个需求,真的是很复杂很个性化,但 sed 命令仍然可以很好地满足:

#先看下文件内容, 第一行中出现了4个Beijing
[roc@roclinux ~]$ cat mysed.txt
Beijing Beijing Beijing Beijing
London London London London

#效果实现啦, 可是, 命令真的好复杂
[roc@roclinux ~]$ sed 's/\(Beijing\)\(.*\)\(Beijing\)/\12008\2\32008/' mysed.txt
Beijing2008 Beijing Beijing Beijing2008
London London London London

这个命令确实足够复杂,用流行的语言说就是“足够虐心”。这个例子中我们再次使用了预存储技术,存储了三项内容,分别代表第一个 Beijing、中间的内容、最后的 Beijing。而针对\1和\3,我们在其后面追加了 2008 这个字符串。

2.2 说一说 y 动作

在介绍 y 动作之前,我们先来看看它可以实现什么效果:

#原文件内容
[roc@roclinux ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#y就是按照字符顺序, 实现前后的替换
[roc@roclinux 20160229]$ sed 'y/ei/ie/' mysed.txt
Biejeng 2003
Biejeng 2004
Biejeng 2005
Biejeng 2006
Biejeng 2007
Biejeng 2008

这个例子其实已经很清楚了,我们希望将所有的 e 和 i 互换。

有些同学会问,y///和s///有什么区别呢?主要有以下两点: y 的语法格式是 y/source/dest/,表示将 source 中的字符对位替换为 dest 中的字符。而 s 的语法格式是 s/regexp/replacement/,表示通过正则匹配到的内容替换为 replacement 部分。 y 只是简单的逐字替换,没有很多花样。s 支持 & 符号和预存储等特性,可以实现更多灵活的替换效果。

这时,一些 GEEK 或许会想到一种情况,那就是 y/ee/ei/ 会产生什么效果呢?因为这里面出现了两个同样的字符,我们还是通过例子来看一下:

[roc@roclinux 20160229]$ sed 'y/ee/ie/' mysed.txt
Biijing 2003
Biijing 2004
Biijing 2005
Biijing 2006
Biijing 2007
Biijing 2008

看到了吧,如果 source 部分出现了重复的字符,则只有第一次出现的对位替换会产生效果,后面的并不会起作用。或许下面这个例子更加清晰些:
#原文件内容
[roc@roclinux ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#iji到iba的替换中, 只有j到b起到了效果
[roc@roclinux 20160229]$ sed 'y/iji/iba/' mysed.txt
Beibing 2003
Beibing 2004
Beibing 2005
Beibing 2006
Beibing 2007
Beibing 2008

2.2 sed d 替换脚本命令

此命令的基本格式为:

[address]d

如果需要删除文本中的特定行,可以用 d 脚本命令,它会删除指定行中的所有内容。但使用该命令时要特别小心,如果你忘记指定具体行的话,文件中的所有内容都会被删除,举个例子:

[root@localhost ~]# cat data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog

[root@localhost ~]# sed 'd' data1.txt
#什么也不输出,证明成了空文件

当和指定地址一起使用时,删除命令显然能发挥出大的功用。可以从数据流中删除特定的文本行。

address 的具体写法后续会做详细介绍,这里只给大家举几个简单的例子:

通过行号指定,比如删除 data6.txt 文件内容中的第 3 行:

[root@localhost ~]# cat data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
[root@localhost ~]# sed '3d' data6.txt
This is line number 1.
This is line number 2.
This is line number 4.

或者通过特定行区间指定,比如删除 data6.txt 文件内容中的第 2、3行:

[root@localhost ~]# sed '2,3d' data6.txt
This is line number 1.
This is line number 4.

也可以使用两个文本模式来删除某个区间内的行,但这么做时要小心,你指定的第一个模式会“打开”行删除功能,第二个模式会“关闭”行删除功能,因此,sed 会删除两个指定行之间的所有行(包括指定的行),例如:

[root@localhost ~]#sed '/1/,/3/d' data6.txt
#删除第 1~3 行的文本数据
This is line number 4.

或者通过特殊的文件结尾字符,比如删除 data6.txt 文件内容中第 3 行开始的所有的内容:

[root@localhost ~]# sed '3,$d' data6.txt
This is line number 1.
This is line number 2.

在此强调,在默认情况下 sed 并不会修改原始文件,这里被删除的行只是从 sed 的输出中消失了,原始文件没做任何改变。

2.3 sed a 和 i 脚本命令

a 命令表示在指定行的后面附加一行,i 命令表示在指定行的前面插入一行,这里之所以要同时介绍这 2 个脚本命令,因为它们的基本格式完全相同,如下所示:

[address]a(或 i)\新文本内容

下面分别就这 2 个命令,给读者举几个例子。比如说,将一个新行插入到数据流第三行前,执行命令如下:

[root@localhost ~]# sed '3i\
> This is an inserted line.' data6.txt
This is line number 1.
This is line number 2.
This is an inserted line.
This is line number 3.
This is line number 4.

再比如说,将一个新行附加到数据流中第三行后,执行命令如下:

[root@localhost ~]# sed '3a\
> This is an appended line.' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is an appended line.
This is line number 4.

如果你想将一个多行数据添加到数据流中,只需对要插入或附加的文本中的每一行末尾(除最后一行)添加反斜线即可,例如:

[root@localhost ~]# sed '1i\
> This is one line of new text.\
> This is another line of new text.' data6.txt

This is one line of new text.
This is another line of new text.
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.

可以看到,指定的两行都会被添加到数据流中。

在文件末尾添加文字

echo "Append this line" >> file.txt

#由于 $ 代表的是最后一行,而 a 的动作是新增,因此该档案最后新增啰! sed 的『 -i 』选项可以直接修改档案内容,这功能非常有帮助

sed 命令远比你想象的要强大,它不仅可以处理本行内容,还可以在这一行的后面插入内容:

#我们将要插入的内容保存到一个单独的文件中
[roc@roclinux ~]$ cat ins.txt
====China====
 
#展示一下我们要处理的文件
[roc@roclinux ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#看, 我们使用r来实现插入
[roc@roclinux ~]$ sed ‘/2005/r ins.txt’ mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
====China====
Beijing 2006
Beijing 2007
Beijing 2008

通过效果可以看出来,我们在文件中的含有 2005 字符串的行的下面一行插入了 ins.txt 文件的内容。

除了可以通过指定文件来插入外,其实还可以使用 a\ 在特定行的“下面”插入特定内容:

#文件内容
[roc@roclinux ~]$ cat new.txt
Beijing 2004
Beijing 2005
Beijing 2006
 
#我们希望在2004的下一行插入China
[roc@roclinux ~]$ sed ‘/2004/a\China’ mysed.txt
Beijing 2003
Beijing 2004
China
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008

可以看到,我们只要使用a\然后加上要插入的内容就可以轻松实现啦。

有些同学会问,既然可以在一行的下面插入内容,那是否可以在一行的上面插入内容呢?答案是当然可以,那就是使用i\动作:

[roc@roclinux ~]$ sed ‘/2004/i\China’ mysed.txt
Beijing 2003
China
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008 

2.4 sed c 替换脚本命令

c 命令表示将指定行中的所有内容,替换成该选项后面的字符串。该命令的基本格式为:

[address]c\用于替换的新文本

举个例子:

[root@localhost ~]# sed '3c\
> This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.

在这个例子中,sed 编辑器会修改第三行中的文本,其实,下面的写法也可以实现此目的:

[root@localhost ~]# sed '/number 3/c\
> This is a changed line of text.' data6.txt
This is line number 1.
This is line number 2.
This is a changed line of text.
This is line number 4.

想将第2-5行的内容取代成为『No 2-5 number』呢?

[root@www ~]# nl /etc/passwd | sed '2,5c No 2-5 number'
 1 root:x:0:0:root:/root:/bin/bash
 No 2-5 number
 6 sync:x:5:0:sync:/sbin:/bin/sync
 .....(后面省略).....

2.5 sed y 转换脚本命令

y 转换命令是唯一可以处理单个字符的 sed 脚本命令,其基本格式如下:

[address]y/inchars/outchars/

转换命令会对 inchars 和 outchars 值进行一对一的映射,即 inchars 中的第一个字符会被转换为 outchars 中的第一个字符,第二个字符会被转换成 outchars 中的第二个字符…这个映射过程会一直持续到处理完指定字符。如果 inchars 和 outchars 的长度不同,则 sed 会产生一条错误消息。

举个简单例子:

[root@localhost ~]# sed 'y/123/789/' data8.txt
This is line number 7.
This is line number 8.
This is line number 9.
This is line number 4.
This is line number 7 again.
This is yet another line.
This is the last line in the file.

可以看到,inchars 模式中指定字符的每个实例都会被替换成 outchars 模式中相同位置的那个字符。

转换命令是一个全局命令,也就是说,它会文本行中找到的所有指定字符自动进行转换,而不会考虑它们出现的位置,再打个比方:

[root@localhost ~]# echo "This 1 is a test of 1 try." | sed 'y/123/456/'
This 4 is a test of 4 try.

sed 转换了在文本行中匹配到的字符 1 的两个实例,我们无法限定只转换在特定地方出现的字符。

2.6 sed p 打印脚本命令 (一般与-n连用)

p 命令表示搜索符号条件的行,并输出该行的内容,此命令的基本格式为:

[address]p

p 命令常见的用法是打印包含匹配文本模式的行,例如:

[root@localhost ~]# cat data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
[root@localhost ~]# sed -n '/number 3/p' data6.txt
This is line number 3.

可以看到,用 -n 选项和 p 命令配合使用,我们可以禁止输出其他行,只打印包含匹配文本模式的行。

如果需要在修改之前查看行,也可以使用打印命令,比如与替换或修改命令一起使用。可以创建一个脚本在修改行之前显示该行, 如下所示:

[root@localhost ~]# sed -n '/3/{
> p
> s/line/test/p
> }' data6.txt
This is line number 3.
This is test number 3.

sed 命令会查找包含数字 3 的行,然后执行两条命令。首先,脚本用 p 命令来打印出原始行;然后它用 s 命令替换文本,并用 p 标记打印出替换结果。输出同时显示了原来的行文本和新的行文本。

透过这个方法我们就能够将数据整行取代了!非常容易吧!sed 还有更好用的东东!我们以前想要列出第 11~20 行, 得要透过『head -n 20 | tail -n 10』之类的方法来处理,很麻烦啦~ sed 则可以简单的直接取出你想要的那几行!是透过行号来捉的喔!看看底下的范例先:

仅列出 /etc/passwd 档案内的第 5-7 行

[root@www ~]# nl /etc/passwd | sed -n '5,7p'
 5 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
 6 sync:x:5:0:sync:/sbin:/bin/sync
 7 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

更聪明的定位行范围

实践是学习知识最好的方法,相信大家看了这个例子后,就明白如何更好地定位行范围了:

#文件内容展示一下

[roc@roclinux ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
Beijing 2007
 
#我们想展示匹配了2005的行和2007的行之间的内容
[roc@roclinux ~]$ sed -n ‘/2005/,/2007/p’ mysed.txt
Beijing 2005
Beijing 2006
Beijing 2007

我们使用 /2005/ 来匹配行范围的首行,用 /2008/ 来匹配行范围的尾行。可以看到,在匹配尾行时,只要遇到第一个符合要求的行,就会停止,而不会再继续向后匹配了。所以,sed 命令只是匹配到了第一个 2007,并没有匹配到第二个 2007。

2.7 sed w 脚本命令

w 命令用来将文本中指定行的内容写入文件中,此命令的基本格式如下:

[address]w filename

这里的 filename 表示文件名,可以使用相对路径或绝对路径,但不管是哪种,运行 sed 命令的用户都必须有文件的写权限。

下面的例子是将数据流中的前两行打印到一个文本文件中:

[root@localhost ~]# sed '1,2w test.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.

[root@localhost ~]# cat test.txt
This is line number 1.
This is line number 2.

当然,如果不想让行直接输出,可以用 -n 选项,再举个例子:

[root@localhost ~]# cat data11.txt
Blum, R       Browncoat
McGuiness, A  Alliance
Bresnahan, C  Browncoat
Harken, C     Alliance

[root@localhost ~]# sed -n '/Browncoat/w Browncoats.txt' data11.txt

cat Browncoats.txt

Blum, R       Browncoat
Bresnahan, C  Browncoat

可以看到,通过使用 w 脚本命令,sed 可以实现将包含文本模式的数据行写入目标文件。

2.8 sed r 脚本命令

r 命令用于将一个独立文件的数据插入到当前数据流的指定位置,该命令的基本格式为:

[address]r filename

sed 命令会将 filename 文件中的内容插入到 address 指定行的后面,比如说:

[root@localhost ~]# cat data12.txt
This is an added line.
This is the second added line.

[root@localhost ~]# sed '3r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is an added line.
This is the second added line.
This is line number 4.	

如果你想将指定文件中的数据插入到数据流的末尾,可以使用 $ 地址符,例如:

[root@localhost ~]# sed '$r data12.txt' data6.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is an added line.
This is the second added line.

2.9 用 -e 选项来设置多个 command

还记得 command 部分吧,现在有一个好消息要告诉你,那就是 sed 命令可以包含不只一个 command。如果要包含多个 command,只需在每个 command 前面分别加上一个-e选项即可。

#我们通过2个-e选项设置了两个command
[roc@roclinux ~]$ sed -n -e ‘1,2p’ -e ‘4p’ mysed.txt
Beijing 2003
Beijing 2004
Beijing 2006

有一个地方值得大家注意,那就是-e选项的后面要立即接 command 内容,不允许再夹杂其他选项。

-e选项支持设置多个 command,这原本是一件好事情,让我们可以更方便地实现一些替换效果。但是,这也给我们带来了幸福的烦恼,假如我们设定了很多个 command,那它们的执行顺序是怎样的呢?

如果这一点不搞明白,-e选项带来的或许只有混乱而非便捷。我们来一起看看下面的例子:

#先看看文件的内容
[roc@roclinux ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#我们设置了两个command
[roc@roclinux ~]$ sed -e ‘s/Beijing/London/g’ -e ‘/Beijing/d’ mysed.txt
London 2003
London 2004
London 2005
London 2006
London 2007
London 2008

前一个 command 表示将 Beijing 替换为 London,而后一个 command 表示要删除包含了 Beijing 字符串的行,但是最后的结果却是输出了所有行,并没有发现被删除的行。这是因为第一个 command 已经将 Beijing 都替换成了 London,所以怪第二个 command 找不到 Beijing 了。

我们再来把上面例子中的 command 颠倒一下位置,看看效果如何:

#我们先指定删除动作, 再指定替换动作
[roc@roclinux 20160229]$ sed -e '/Beijing/d' -e 's/Beijing/London/g' mysed.txt
[roc@roclinux 20160229]$

通过这两个小例子,我们可以很清晰地看到,多个 command 之间,是按照在命令中的先后顺序来执行的。

2.10 用 -f 选项设定 command 文件

如果你的 sed 命令的 command 部分很长,那么可以将内容写到一个单独的文件中,然后使用-f选项来指定这个文件作为我们 sed 命令的 command 部分:

#这是我们事先写好的文件
[roc@roclinux ~]$ cat callsed
/2004/,/2006/p
 
#我们用-f选项来指定command文件
[roc@roclinux ~]$ sed -n -f callsed mysed.txt
Beijing 2004
Beijing 2005
Beijing 2006

很好理解吧,-f选项并不难,而且我会经常使用,因为一些比较常用的匹配规则,我都会存到单独的文件中,不用再费脑子记忆啦。

2.11 将指定行写入到特定文件中

文章要进入尾声了,我们最后再教大家一个非常实用的动作,那就是 w 动作,它可以将匹配到的内容写入到另一个文件中,即用来实现内容的筛选与保存:

#将包含2004、2005、2006的行保存到new.txt文件中
[roc@roclinux ~]$ sed ‘/200[4-6]/w new.txt’ mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#我们要的内容已经乖乖到碗里来了
[roc@roclinux ~]$ cat new.txt
Beijing 2004
Beijing 2005
Beijing 2006

2.11 sed q 退出脚本命令

q 命令的作用是使 sed 命令在第一次匹配任务结束后,退出 sed 程序,不再进行对后续数据的处理。

比如:

[root@localhost ~]# sed '2q' test.txt
This is line number 1.
This is line number 2.

可以看到,sed 命令在打印输出第 2 行之后,就停止了,是 q 命令造成的,再比如:

[root@localhost ~]# sed '/number 1/{ s/number 1/number 0/;q; }' test.txt
This is line number 0.

使用 q 命令之后,sed 命令会在匹配到 number 1 时,将其替换成 number 0,然后直接退出。

二、sed 脚本命令的寻址方式

前面在介绍各个脚本命令时,我们一直忽略了对 address 部分的介绍。对各个脚本命令来说,address 用来表明该脚本命令作用到文本中的具体行。

默认情况下,sed 命令会作用于文本数据的所有行。如果只想将命令作用于特定行或某些行,则必须写明 address 部分,表示的方法有以下 2 种:

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

以上两种形式都可以使用如下这 2 种格式,分别是:

[address]脚本命令

或者

address {
    多个脚本命令
}

以上两种形式在前面例子中都有具体实例,因此这里不再做过多赘述。

3.1 以数字形式指定行区间

当使用数字方式的行寻址时,可以用行在文本流中的行位置来引用。sed 会将文本流中的第一行编号为 1,然后继续按顺序为接下来的行分配行号。

在脚本命令中,指定的地址可以是单个行号,或是用起始行号、逗号以及结尾行号指定的一定区间范围内的行。这里举一个 sed 命令作用到指定行号的例子:

[root@localhost ~]#sed '2s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy dog

可以看到,sed 只修改地址指定的第二行的文本。下面的例子中使用了行地址区间:

[root@localhost ~]# sed '2,3s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy dog

在此基础上,如果想将命令作用到文本中从某行开始的所有行,可以用特殊地址——美元符($):

[root@localhost ~]# sed '2,$s/dog/cat/' data1.txt
The quick brown fox jumps over the lazy dog
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat
The quick brown fox jumps over the lazy cat

3.2 用文本模式指定行区间

sed 允许指定文本模式来过滤出命令要作用的行,格式如下:

/pattern/command

注意,必须用正斜线将要指定的 pattern 封起来,sed 会将该命令作用到包含指定文本模式的行上。

举个例子,如果你想只修改用户 demo 的默认 shell,可以使用 sed 命令,执行命令如下:

[root@localhost ~]# grep demo /etc/passwd
demo:x:502:502::/home/Samantha:/bin/bash
[root@localhost ~]# sed '/demo/s/bash/csh/' /etc/passwd
root:x:0:0:root:/root:/bin/bash
...
demo:x:502:502::/home/demo:/bin/csh
...

虽然使用固定文本模式能帮你过滤出特定的值,就跟上面这个用户名的例子一样,但其作用难免有限,因此,sed 允许在文本模式使用正则表达式指明作用的具体行。正则表达式允许创建高级文本模式匹配表达式来匹配各种数据。这些表达式结合了一系列通配符、特殊字符以及固定文本字符来生成能够匹配几乎任何形式文本的简练模式。

关于正则表达式,本节不做过多介绍,有兴趣的读者可阅读《正则表达式入门教程》一文,这里仅给读者提供一个简单示例:

[root@localhost ~]# cat test.txt
<html>
<title>First Wed</title>
<body>
h1Helloh1
h2Helloh2
h3Helloh3
</body>
</html>
#使用正则表示式给所有第一个的h1、h2、h3添加<>,给第二个h1、h2、h3添加</>
[root@localhost ~]# cat sed.sh
/h[0-9]/{
    s//\<&\>/1
    s//\<\/&\>/2
}
[root@localhost ~]# sed -f sed.sh test.txt
<h1>Hello</h1>
<h2>Hello</h2>
<h3>Hello</h3>

范例七:利用 sed 直接在 regular_express.txt 最后一行加入『# This is a test』

[root@www ~]# sed -i '$a # This is a test' regular_express.txt

3.3 通过 n 动作来控制行的下移

有时我们希望实现隔行处理的效果,比如只需对偶数行做某个替换,这时候,我们就需要 n 动作的帮忙啦:

#原文件内容
[roc@roclinux ~]$ cat mysed.txt
Beijing 2003
Beijing 2004
Beijing 2005
Beijing 2006
Beijing 2007
Beijing 2008
 
#我们同时使用了n动作和y动作
[roc@roclinux ~]$ sed ‘/200/{n;y/eijng/EIJNG/;}’ mysed.txt
Beijing 2003
BEIJING 2004
Beijing 2005
BEIJING 2006
Beijing 2007
BEIJING 2008

你会发现,大写的 BEIJING 是隔行出现的。这就是n选项在起作用,它的真实作用是将下一行内容放到处理缓存中,这样,就让当前这一行躲避开了替换动作,是不是有点像小时候玩游戏时通过左右键躲避开 BOSS 的大招,哈哈。

五、个人案例

5.1 pmid = PMID: 24097066,替换为 pmid = “24097066”,

sed -ir 's/PMID: (.*),/"\1",/g' 1701__C_disease.lua        #-i 替换

将连续多个空格替换为一个tab

sed -i 's/\s\+/\t/g' file

参考资料

药企,独角兽,苏州。团队长期招人,感兴趣的都可以发邮件聊聊:tiehan@sina.cn
个人公众号,比较懒,很少更新,可以在上面提问题,如果回复不及时,可发邮件给我: tiehan@sina.cn