Sed 代表 Strem Editor(流编辑器),是操作、过滤和转换文本内容的强大工具。 它最初是为 AT&T 最初的 Unix 操作系统第 7 版创建的,此后可能每一个 Unix 和 Linux 操作系统都包含了它。 sed 以行为处理单位,针对每一行进行处理。 功能上同awk类似,sed功能更简单,针对列处理功能要差很多。

1、sed 命令语法

1
sed [options] '{sed-commands}' {input-file}

可选参数

  • -n, --quiet, --silent: 仅显示sed-commands处理后的结果
  • -e script, --expression=script: 指定sed-commands来处理输输入的文件
  • -f script-file, --file=script-file: {sed-commands}即可是单个命令也可以是多个命令,将多个命令合并到一个文件(被称为:sed脚本)后,可以使用-f选项调用它
  • -i: 直接修改原始文件内容(危险动作,需要慎重使用)
  • --version: 显示版本号

2、sed 脚本执行流程

sed脚本执行的步骤很容易记住:读取(Read),执行(Execute),打印(Print),重复(Repeat)。我们可以利用这几个步骤的首字母REPR来记忆sed执行的步骤。

我们来看一下这几个步骤。 sed将会:

  • 读取一行内容到模式空间(sed内部的一个临时缓存,用于存放读取到的内容)
  • 执行:对模式空间里的内容执行sed命令。 如果使用了 {} 或 -e 指定多个命令,sed将依次执行每个命令
  • 打印(输出)模式空间的内容。然后清空模式空间
  • 重复以上流程,直到文件结束

sed

3、sed 命令

  • a: 追加命令,可以在指定位置后面插入新行
  • i: 插入命令,在指定位置之前插入新行
  • c: 修改命令,取代指定位置旧行
  • d: 删除命令,删除指定行
  • p: 打印命令,打印当前模式空间的内容,通常和可选参数-n一同使用
  • s: 替换命令,进行字符串替换
  • y: 转换字符,根据对应位置转换字符,如进行大小写转换
  • =: 打印行号,会在每一行的后显示改行的行号
  • q: 终止正在执行的命令并退出sed。sed正常执行流程是读取数据、执行命令、打印结果、重复循环。当sed遇到q命令时,便停止执行后续循环立即退出
  • r: 从指定的文件读取内容,并在指定的位置将其打印出来

4、实例技巧

有一段歌词保存在文件myheart,内容如下:

1
2
3
1 Every night in my dreams
2 I see you, I feel you
3 That is how I know you go on

完整歌词可以在My Heaert Will Go On 获取完成歌词

4.1、追加行

使用 a 命令可以在指定位置后插入新行,语法格式:

1
$ sed '[address] a the-line-to-append' input-file
4.1.1、在第一行后追加一行
1
2
3
4
5
$ sed '1a hello world' myheart
1 Every night in my dreams
hello world
2 I see you, I feel you
That is how I know you go on

在mac上使用系统自带sed版本执行时,会返回错误信息sed: 1: "1a hello world": command a expects \ followed by text。 可以安装GNU版sed来解决:

brew install gnu-sed

alias sed=gsed

4.1.2、在最后一行追加一行
1
2
3
4
5
$ sed '$a Far across the distance' myheart
1 Every night in my dreams
2 I see you, I feel you
3 That is how I know you go on
Far across the distance
4.1.3、在匹配行后追加一行
1
2
3
4
5
6
$ sed '/you/a Far across the distance' myheart
1 Every night in my dreams
2 I see you, I feel you
Far across the distance
3 That is how I know you go on
Far across the distance

4.2、新行取代旧行

使用c命令可以,用给定的行替换指定位置的旧行。

4.2.1、用新数据取代第一行
1
2
3
4
$ gsed '1c Far across the distance' myheart
Far across the distance
2 I see you, I feel you
3 That is how I know you go on

将第一行替换为Far across the distance

4.2.2、用新数据替代匹配you的行
1
2
3
4
$sed '/you/c You have come to show you go on' myheart
1 Every night in my dreams
You have come to show you go on
You have come to show you go on
4.2.3、用多行新数据替代匹配you的行
1
2
3
4
5
6
7
$sed '/you/c Far across the distance\
>And spaces between us' myheart
1 Every night in my dreams
Far across the distance
And spaces between us
Far across the distance
And spaces between us

/you/匹配到了两行,所以这两行都进行了替换

4.2.4、每一行后新增空白行
1
2
3
4
5
6
$ sed G myheart         
1 Every night in my dreams

2 I see you, I feel you

3 That is how I know you go on

G 命令把当前保持空间的内容作为新行追加到模式空间中, 模式空间的内容不会被覆 盖,该命令在模式空间后面加上换行符\n,然后把保持空间内容追加进去。

4.5、删除行

4.5.1、删除第一行
1
2
3
$ sed '1d' myheart
2 I see you, I feel you
3 That is how I know you go on
4.5.2、保留第一行其余都删除
1
2
$ sed '1!d' myheart
1 Every night in my dreams
4.5.2、删除最后一行
1
2
3
$ sed '$d' myheart
1 Every night in my dreams
2 I see you, I feel you
4.5.3、删除匹配行
1
2
3
$ sed '/Every/d' myheart
2 I see you, I feel you
3 That is how I know you go on
4.5.4 删除2开头的行
1
2
3
$ sed '/^2/d' myheart    
1 Every night in my dreams
3 That is how I know you go on
4.5.5 删除从第一次匹配行到最后一行
1
2
$ sed '/^2/,$ d' myheart
1 Every night in my dreams
4.5.6 删除从第一次匹配行和它后面的一行
1
2
$ sed '/^2/,+1 d' myheart
1 Every night in my dreams
4.5.7 删除从第一次匹配行到第二行
1
2
$ sed '/Every/,2 d' myheart
3 That is how I know you go on

4.6、搜索显示数据

4.6.1、显示第一行

sed在执行完成命令后会默认打印模式空间的内容,而命令p也会输出当前模式空间的内容。所以, 通常使用命令p时,还需要使用-n选项来屏蔽sed的默认输出,否则使用p命令后,每行记录会输出两次。

1
2
$sed -n '1p' myheart
1 Every night in my dreams
4.6.2、显示最后一行
1
2
$ sed -n '$p' myheart
3 That is how I know you go on
4.6.3、显示第二到第三行
1
2
3
$ sed -n '2,3p' myheart
2 I see you, I feel you
3 That is how I know you go on
4.6.4、搜索显示匹配的行
1
2
$ gsed -n '/Every/p' myheart
1 Every night in my dreams

显示包含Every的行

4.7、 替换数据

sed除了整行的新增、删除、替换外,还可以行为单位进行部分数据的查找替换。

替换命令语法

1
sed '[address-range|pattern-range] s/original-string/replacement-string/[substitute-flags]' input file
  • address-rangepattern-range (即地址范围或模式范围)是可选的,如果没有指定,那么 sed 将在所有行上进行替换
  • s 即执行替换命令 substitute
  • original-string 是被 sed 搜索然后被替换的字符串,它可以是一个正则表达式
  • replacement-string 是替换后的字符串
  • substitute-flags 是可选的
4.7.1、用 you 替换 YOU
1
2
3
4
$ sed 's/you/YOU/' myheart
1 Every night in my dreams
2 I see YOU, I feel you
3 That is how I know YOU go on
4.7.2、全局标志

在上面的例子(4.7.1)可以发现第2行中有两个 you 只有第一个被替换成 YOU,因为默认情况下,sed会替换每行中第一个original-string,可以使用 全局标志 g 将每行中所有匹配项都进行替换。

1
2
3
4
$ sed 's/you/YOU/g' myheart
1 Every night in my dreams
2 I see YOU, I feel YOU
3 That is how I know YOU go on
4.7.3、在指定的目标范围进行替换

4.7.1中,第2、3行中的you 被成功替换成 YOU,如果只想要期中的部分行进行替换,那么可以通过直接指定地址范围 或者模式范围,而不是默认的所有行上,进行替换操作。

指定地址范围

只在第三行进行,将 you 替换 YOU

1
2
3
4
$ sed '3 s/you/YOU/g' myheart
1 Every night in my dreams
2 I see you, I feel you
3 That is how I know YOU go on

指定模式范围

在包含 know 的行,将 you 替换 YOU

1
2
3
4
$ sed '/know/ s/you/YOU/g' myheart
1 Every night in my dreams
2 I see you, I feel you
3 That is how I know YOU go on
4.7.4、指定匹配的original-string次序进行替换

4.7.1中,第二行中有两个 you 可以匹配到,默认是第一个匹配项被替换,可以通过指定数字标志来指定次序匹配项被替换。 如,要第二个匹配项 you 被替换,可以使用:

1
2
3
4
$ sed 's/you/YOU/2' myheart
1 Every night in my dreams
2 I see you, I feel YOU
3 That is how I know you go on

使用s/you/YOU/2 后,每一行都是第二个匹配项才会被替换,所以第三行的 you 没有被替换。

4.7.5、只打印替换后的行

4.7.4中,将每行中的第二个 you 替换成 YOU, 可以配置 p命令和 -n 选项,将发生替换的行显示出来,而其他行不输出。

1
2
$ sed  -n 's/you/YOU/2p' myheart
2 I see you, I feel YOU
4.7.6、忽略大小

可以使用i标志,在模式匹配中忽略大小写,进行匹配替换。如可以改写4.7.5,使用yOu进行匹配替换:

1
2
sed  -n 's/yOu/YOU/2pi' myheart
2 I see you, I feel YOU

如果没有i标志时,就不会发生更改替换:

1
2
3
4
5
6
$ sed 's/yOu/YOU/2' myheart 
1 Every night in my dreams
2 I see you, I feel you
3 That is how I know you go on

$ sed  -n 's/yOu/YOU/2p' myheart
4.7.7、大小写替换

进行大小写替换可以使用命令 y, 该命令根据对应位置进行字符转换:

1
2
3
4
$ sed 'y/you/YOU/' myheart 
1 EverY night in mY dreams
2 I see YOU, I feel YOU
3 That is hOw I knOw YOU gO On

将所有的y->Yo->O,u->U,也可以和4.7.3一样进行指定范围或模式范围进行限定,这样可以避免在所有的行进行操作。

也可以在命令 s 中,使用特殊的功能来实现。如:\L 将匹配文本替换为小写,\l 则只是转换下一个字符为小写,\U 将匹配的文本替换为大写, \u 将只是将下一个字符转为大写,& 指的是匹配到的模式:

1
2
3
4
$ gsed 's/you/\U&/' myheart
1 Every night in my dreams
2 I see YOU, I feel you
3 That is how I know YOU go on

同样的可以使用 g 标志,将所有匹配项都进行替换:

1
2
3
4
$ gsed 's/you/\U&/g' myheart
1 Every night in my dreams
2 I see YOU, I feel YOU
3 That is how I know YOU go on
4.7.8、每一行后添加一行空白行

可以使用 G 命令,也可以是用 s 命令完成,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ sed G myheart 
1 Every night in my dreams

2 I see you, I feel you

3 That is how I know you go on

$ sed 's/.*/&\n/' myheart
1 Every night in my dreams

2 I see you, I feel you

3 That is how I know you go on

G 命令把当前保持空间的内容作为新行追加到模式空间,模式空间中的内容不会被覆盖, 该命令在模式空间后面加上换行符 \n,然后保持空间内容添加进去,sed G myheart 中保持空间没有内容, 所以达到仅仅只是换行的目的。

sed 's/.*/&\n/' myheart 使用 s 命令利用正则表达式匹配一整行,并是用 & 表示当期匹配的内容,紧跟 换行符 \n,达到每一行后添加一个换行的目的。

4.8、批量修改文件

用可选项 -i 可以直接修改原始文件内容,如:

1
sed -i 's/one/two' demo.txt

上面的命令,将文件中demo.txt,每行中的第一个 one 修改成 two。这条命令在Mac 系统中会提示错误(Linux 能正常运行):

1
sed: 1: "demo.txt": command c expects \ followed by text

对于这个问题,StackOverflow有个答案:sed command with -i option failing on Mac, but works on Linux

下面有一个不错的方案:

This works with both GNU and BSD versions of sed:

1
sed -i'' -e 's/old_link/new_link/g' *

or with backup:

1
 sed -i'.bak' -e 's/old_link/new_link/g' *

如果没有其他备份机制,使用-i'.bak来保存备份,来避免错误修改且丢失原始文件造成灾难。

回到主题:批量修改文件,可以联合 xargsfindrg 命令来完成。

如当前目录下有大量的 Markdown 文件,需要删除文件中的一行 contentCopyright: true,那么可以使用下面的命令:

1
$ rg --type md --files-with-matches 'contentCopyright: true' | xargs sed -i'' '/contentCopyright: true/d'

首先使用 rg 找到包含 contentCopyright: trueMarkdown 文件, 然后交给 sed 修改。

  • --type md 指定 rg 在Markdown文件中进行匹配
  • --files-with-matches 选项指定 rg 只返回匹配项的文件, 不返回匹配的内容详情
  • -i'' sed 直接修改原始文件内容

5、资源