Linux-Sed命令

背景

之前讲到文本编辑的时候,讲到Vim编辑器,在shell命令行下有很多类编辑器,比如:vi、emacs、vim、nano等,它们作为一个独立的软件包,对我们还是很友好的,但是当我们需要对文本进行自动化处理的时候,它们就无法很好的使用了,因此就引入了流编辑器的概念,其中之一便是sed命令

内容

入门

作为流编辑器,它有如下几个特点:

  1. 每次从输入中读取一行
  2. 根据提供的编辑器命令匹配数据,并根据命令修改流中的数据
  3. 将新的数据输出到STDOUT中,而不是回写至文件中,除非人为设定

它的格式如下:

1
2
3
4
# option:指定参数
# script:操作文本的指令,可以称之为替换标记,和-e、-f参数搭配使用
# file:待处理的文本文件
sed option script file

比如:

1
2
3
4
5
6
7
8
9
10
# 如果有多个命令,则之间用;隔开
sed -e "s/shuai/baqi/;s/devin/shuai" file

# 也可以指定多个-e
sed -e "s/shuai/baqi/“ -e "s/devin/shuai" file

# 同样还可以像内联重定向那样输入
sed -e "
> s/shuai/baqi/
> s/devin/shuai" file

如果含有大量的sed命令,则可以单独创建sed命令文件,然后使用-f引入该文件:

1
2
3
4
5
6
7
# 建议将sed命令文件加后缀.sed
# sed命令文件中一行一条命令, 内容类似如下:
s/baqi/shuai/
s/devin/niubi/

# 使用sed命令
sed -f operation.sed file

sed和其它常规命令一样,它也有众多的参数,参数的不同意味着不同的功能。but,在sed中,除了参数,还有替换标记中的一些指令有其特殊的含义。比如刚刚讲述的s就是替换文本的标识,它完整的格式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
s/pattern/replacement/flags

# flags为数字:表示只替换一行中指定第数字个匹配的字符
sed -e "s/devin/shuai/2" file

# flags为g:替换一行中所有匹配的字符
sed -e "s/devin/shuai/g" file

# flags为p:替换一行中所有匹配的字符,一般与参数-n一起使用
sed -e "s/devin/shuai/p" file

# flags为w:表示将替换后的内容回写到指定的文件中,它只将替换行的内容输出到指定文件中,未替换行的内容不会写到新文件中
sed -e "s/devin/shuai/gw out.txt" file

对于替换标记中的间隔符号,除了传统的/以外,也可以指定其它的字符,如!:

1
2
3
4
5
# 如果替换内容中有/,则需要用\进行转移
sed -e "s/\/bin\/bash/shuai/p" file

# 或者就用别的字符,如!,!外用'包裹,否则shell会将其当作调用历史命令的指令理解
sed -e 's!devin!shuai!p' file

上述的方式会将替换的指令作用于文本中所有的行,如果只是想作用于文本中指定的行,则可以借助行寻址,有两种方式:通过数字寻址、文本模式寻址。通过数字寻址的方式如下:

1
2
3
4
5
6
7
8
9
10
11
# 格式
[address]command

# 只修改第二行匹配的内容
sed -e "2s/devin/shuai/g" file

# 修改第2~5行匹配的内容
sed -e "2,5s/devin/shuai/g" file

# 修改第2到文末行匹配的内容
sed -e "2,$s/devin/shuai/g" file

如果需要对同一行执行多条命令,则需要借助于{}:

1
2
3
4
sed -e "2,5{
> s/baqi/shuai/g
> s/niu/bi/g
}" file

通过文本寻址的方式如下:

1
2
3
4
5
6
# pattern会匹配到符合条件的行上,它也支持通过正则进行匹配
# 格式:
/pattern/command

# 只修改有shuai的行
sed -e "/shuai/s/name/devin/g" file

除了s替换指令外,还有删除指令d

1
2
3
4
5
6
7
8
9
# 格式:
[address]d

# 删除第3到5行,这个是通过数字寻址
sed -e "3,5d" file

# 也可以通过文本寻址,它会删除起始匹配3到匹配5与其直接所有的行,
# 一旦匹配3,则开始删除,如果没有匹配终止5,则一直删除到文末
sed -e "/3/,/5/d" file

还有增加的命令:i、a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 格式:
[address]i

# 在第5~7行前都插入一行内容
sed -e "5,7i\baqi" file

# \也可以用空格代替
sed -e "5,7i baqi" file

# 对于a也是同理,在第5~7行后都追加一行内容
sed -e "5,7a baqi" file

# 如果想追加到文末,则借助于$
sed -e "$a baqi" file

如果需要一次性在指定位置插入多行数据,则在每行内容后追加\n换行符,如下:

1
sed -e "3i\nbaqi\nniubi\nshuai" file

然后就是修改行的命令:c

1
2
3
4
5
6
7
8
# 格式:
[address]c\content

# 比如将第3行的内容换成我们想要的
sed -e "3c\shuaiqi" file

# 也可以借助文本寻址的方式,将baqi所在行换掉
sed -e "/baqi/c\shuaiqi" file

但是如果你想用一行内容替换掉多行内容则不行,sed会依次用给定的行替换选区内的行,替换完了则剩余行的内容不变。

另外还有一个转换字符的命令:y,它会替换掉指定的单个字符,并且它是一个全局命令,这意味着无法指定选区,它作用于整个文档:

1
2
3
4
5
6
7
# 运作原理:它使用inchars的第一个字符替换为outchars的第一个字符,以此类推。
# inchars和outchars字符数量必须相同,否则替换会报错
# 格式:
y/inchars/outchars/

# 比如,将文本中2替换为3,4替换为1
sed -e "y/24/31/" file

接着就是打印内容的命令:p=l,其中p用于输出内容,=专注于打印行号,l可以打印文本中肉眼不可见字符:

1
2
3
4
5
6
7
8
9
10
11
# 格式
[address]command

# 打印第5行的内容
sed -e "5p" file

# 打印文本匹配的行号
sed -e "/shuai/=" file

# 打印第5行中肉眼不可见的字符
sed -e "5l" file

sed中,还可以将指定的内容保存到新的文件中,这个需要借助于命令:w

1
2
3
4
5
6
# filename是文件的路径,相对、绝对都可以
# 格式:
[address]w filename

# 比如:将有shuaiqi的行全部输出到devin文件中
sed -e "/shuaiqi/w devin" file

同样还可以在一个文件的文件流中读取其他文件的内容,这个需要借助于命令:r

1
2
3
4
5
6
7
8
9
10
11
12
# filename是文件的路径,相对、绝对都可以
# 格式
[address]r filename

# 比如,在第2行追加新文件的内容
sed -e "3r filename" file

# 如果借助同行多命令的操作,则可以实现用新文件的内容替换原文件的内容
sed -e "3{
r filename
d # 需要先插入再删除
}" file

高级

入门中,讲述的内容都是针对单行内容的,可是如果我们遇到了需要处理跨行内容的时候,入门知识就无法完全适用了,这个时候就会讲到跨行命令,比如,如果我们需要清理定位元素下一行的内容,这个时候就需要借助命令:n

1
2
3
4
5
6
# n:表示处理定位元素的下一行,定位的行不修改
# 格式:
[address]{n; command}

# 比如删除定位元素的下一行内容
sed -e "/name/{n; d}" file

假如我们需要对两个连续行的内容合并在一起处理时,此时需要借助命令:N

1
2
3
4
5
6
# N:表示将相邻的两行元素合并在一起进行处理,注意换行符还是存在的
# 格式:
[address]{N; command}

# 替换跨行匹配的字符name shuai为霸气
sed -e "N; s/name\nshuai/baqi/" file

关于N,需要注意,虽然将两行当成一行处理,但是跨行中的换行符还是存在的\n,同时,它是在匹配address后开始依次将连续的两行当成一行进行处理,比如将1、2当成一个对象,再将3、4当成一个对象,不存在将2、3再组成一个对象处理。

之前讲过删除行的命令d,它只能作用于选择域内,如果我们希望删除定位位置的上一行,此时则需要借助命令:D

1
2
3
4
5
6
# D:删除模式空间中的第一行
# 格式:
[address]D

# 比如删除两行中匹配line的第一行
sed -e "N; /head/D" file

关于D,它作用的是一个模式空间,所以一般跟N协作,此外它只会删除匹配的模式空间的第一行,即便匹配到的内容是第二行也不行。

然后就是和打印命令p相对应的P,它也是只打印模式空间中的第一行内容,即使匹配的内容是模式空间中的第二行也不行

1
2
3
4
5
6
7
# P:打印模式空间中的第一行
# 格式:
[address]P

# 比如打印每个匹配模式中的第一行内容
# -n:表示只显示匹配处理的行
sed -n -e "N; P" file

说到此处,就聊到了sed文本流工作的方式:逐行读取并运行,所以如果用p、P直接打印的话,它是读一行打印一行的。

上面提及的模式空间其实就是我们操作的每一个文本行对象,sed每开始读取一行数据的时候,就会将读取的内容放到模式空间中(理解为工作空间),与此相对应的还有保持空间的概念,保持空间可以理解为Windows下的剪贴版,用于保存内容的,起始情况下,保持空间中存有一个空行。在sed中,如果我们需要将模式空间的内容添加到保存空间,则需要用到指令:h、H

1
2
3
4
5
6
7
8
9
10
# h:表示将模式空间的内容提取到保存空间
# H:表示将模式空间的内容追加到保存空间
# 格式
[address]h

# 比如想将有first的行与它的下一行进行互换
sed -n -e "/first/{h;n;p;g;p}" file

# 将first的行与其下一行打印到下下一行
sed -n -e "/first/{h;n;H;n;p;g;p}" file

上面的内容不是看得有点懵,默认情况下,sed是读取一行内容就处理一行内容,如果不指定-n选项,则sed会根据情况,不处理的行会直接输出,然后执行处理的命令,并输出处理后的结果;如果指定了-n选项,则sed的输出内容就完全取决于用户在什么位置p,否则就算是内容进行了修改,STDOUT也不会有任何的输出。

带着这样的理解,再回头看上面的命令:

1
2
3
sed -n -e "/first/{h;n;p;g;p}" file

# 定位first所在行(first),然后将该行提取到保存空间中(h),然后漂移到下一行(n),输出下一行的内容(p),然后将保持空间的内容复制到模式空间(g),然后p处这个保持空间的内容(p)

记住一点:p只能打印模式空间的内容,同样,如果需要将保持空间的内容复制到模式空间的话,则可以借助命令:g、G

1
2
3
4
5
6
7
# g:将保持空间内容复制到模式空间
# G:将保持空间内容附加到模式空间,相当于将当前行追加到剪贴版的顶部位置
# 格式:
[address]g

# 比如将一个文本的所有行进行反转输出
sed -n -e '1!G;h;$p' file

还有另外一个指令:x,用户交换保持空间模式空间中的内容

1
2
# 格式和上面的一行
sed -n -e 'x;p' file

不过Linux下也已经为我们提供了文本反转输出的命令:tac

1
tac file

有时候希望某些命令不会作用于指定的区域,则可以借助命令:!,但是一定要注意:包裹修改命令的部分一定要是单引号,否则shell会以为我们想要调用历史命令

1
2
# 表示除了第一行以外,其它行都执行打印命令
sed -n -e "1!p" file

同样还可以借助于分支命令:b,实现对指定区域内的内容执行部分命令

1
2
3
4
5
6
7
8
9
10
11
# label:指示跳转的位置,如果没有,则表示跳转到命令的结束位置
# label位置的格式为:[:label],字符数最多为6位
# 格式:
[address]b [label]

# 比如:一旦某一行匹配了first,则直接跳转到:jump的位置
wudashuai@localhost: sed -n -e "
> /first/b jump
> s/lod/new/
> :jump
> s/shuai/baqi/" file

如果将label的位置置于了跳转之前,则还可以形成一个循环的效果

1
2
3
4
5
wudashuai@localhost: sed -n -e "
> :start
> s/,//
> p
> /,/b start" file

此外sed还提供了测试(test)命令:t,它就像程序中的if判断语句一样,它会根据替换命令的执行结果来决定是否跳转,如果替换成功,则不执行跳转,继续执行之后的操作;反之则可以执行跳转。

1
2
3
4
5
6
7
8
9
# 规则和b一样
# 格式:
[address]t [label]

# 比如将上面的例子进行改写
wudashuai@localhost: sed -n -e "
> :start
> s/,/ /1p # 一旦这个替换命令成功,则t不执行跳转
> t start" file

同时,sed中的替换命令s支持分组引用,比如对匹配内容的引用就可以借助于命令:&

1
2
# 它就会将正则匹配的内容外部追加"
sed -n -e 's/sh.ai/"&"/"' file

同样也可以采用分组,然后通过\num的方式引用,但是需要注意,分组的括号一定要用\进行转义,否则会被shell当成字符处理

1
2
# 会将第一个框号的内容与第二个括号的内容进行互换
sed -n -e "s/\(.uai\) baqi \(niubi\)/\2-\1/p" file

之前讲过分支命令b,其实它就像循环,对应的sed还提供了一个跳出循环的命令:q

1
2
3
4
5
6
7
8
# 格式:
[address]q

# 比如,当文本处理到最后一行的时候,就直接跳出循环
wudashuai@localhost: sed -n -e "
> :start
> $q;N;s/\n/ /
> b start" file

剩下的,就是无尽的基操了!去吧,少年,经历社会的毒打吧!!!