介绍
sed
流编辑器是一款功能强大的编辑工具,只需很少的输入即可进行彻底的更改。 在上一篇关于 sed
的文章中,您探索了 使用 sed 编辑文本的基础知识。
本文将通过检查一些更高级的主题来继续您的介绍。
注意:本教程使用在 Ubuntu 和其他 Linux 操作系统上找到的 sed
的 GNU 版本。 如果您使用的是 macOS,您将拥有具有不同选项和参数的 BSD 版本。 您可以使用 brew install gnu-sed
安装带有 Homebrew 的 sed
的 GNU 版本。
启动交互式终端!
要完成本教程,您将需要一些文件来操作,您应该从第一个教程中获得这些文件。 如果您没有它们,您可以使用以下命令重新创建它们:
cd cp /usr/share/common-licenses/BSD . echo "this is the song that never ends yes, it goes on and on, my friend some people started singing it not knowing what it was and they'll continue singing it forever just because..." > song.txt
此外,您将在本教程中使用 GPL 3 许可证 ,因此也要复制该文件:
cp /usr/share/common-licenses/GPL-3 .
如果没有,可以用curl
下载:
curl -o GPL-3 https://www.gnu.org/licenses/gpl-3.0.txt
现在您已经有了这些文件,您将通过多个命令来探索使用 sed
。
提供多个编辑序列
在很多情况下,您可能希望同时向 sed
传递多个命令。 有几种方法可以实现这一点。
由于 sed
通过标准输入和输出进行操作,因此您可以通过管道将对 sed
的不同调用串在一起。 执行此命令将单词 and
替换为和号 (&
),将单词 people
替换为 horses
:
sed 's/and/\&/' song.txt | sed 's/people/horses/'
请注意,您需要转义“&”,因为它意味着“完全匹配的模式”sed
):
您将看到以下输出:
Outputthis is the song that never ends yes, it goes on & on, my friend some horses started singing it not knowing what it was & they'll continue singing it forever just because...
这是可行的,但是多次调用 sed
会产生不必要的开销,需要更多的输入,并且没有利用 sed
的内置功能。
您可以通过在每个命令之前使用 -e
选项将各种命令字符串到 sed
。 这就是你将如何重写前面的命令:
sed -e 's/and/\&/' -e 's/people/horses/' song.txt
另一种将命令串在一起的方法是使用分号字符 (;
) 来分隔不同的命令。 这与前面的示例相同,但不需要“-e
”:
sed 's/and/\&/;s/people/horses/' song.txt
请注意,在使用 -e
构造时,您需要为不同的命令使用单独的单引号组。 但是,当用分号分隔命令时,所有命令都放在一个单引号命令字符串中。 尽管这两种表达多个命令的方式很有用,但有时仍需要以前的管道技术。
考虑 =
运算符。 此运算符在每个现有行之间的新行上插入行号。 输出如下所示:
sed '=' song.txt
这是您将看到的输出:
Output1 this is the song that never ends 2 yes, it goes on and on, my friend 3 some people started singing it 4 not knowing what it was 5 and they'll continue singing it forever 6 just because...
但是,如果您想通过修改文本来更改编号的格式,您会发现事情并没有按预期工作。
为了演示,让我们看一下 G
命令,默认情况下,它在每行之间输入一个空行(这实际上更复杂,但您稍后会探讨):
sed 'G' song.txt
结果如下:
Outputthis is the song that never ends yes, it goes on and on, my friend some people started singing it not knowing what it was and they'll continue singing it forever just because...
如果将这两个命令结合起来,您可能会希望每个常规行和行号行之间有一个空格:
sed '=;G' song.txt
但是,您会得到不同的东西:
Output1 this is the song that never ends 2 yes, it goes on and on, my friend 3 some people started singing it 4 not knowing what it was . . . . . .
发生这种情况是因为 =
运算符直接修改了实际的输出流。 这意味着您不能将结果用于更多编辑。
您可以通过使用两个 sed
调用来解决此问题,将第一个 sed
修改视为第二个简单的文本流:
sed '=' song.txt | sed 'G'
您现在看到了预期的结果:
Output1 this is the song that never ends 2 yes, it goes on and on, my friend 3 some people started singing it . . . . . .
请记住,某些命令的运行方式是这样的,尤其是当您将多个命令串在一起并且输出与您期望的不同时。
高级寻址
sed
的可寻址命令的优点之一是正则表达式可以用作选择标准。 这意味着您不仅限于对已知的行值进行操作,就像您之前看到的那样:
sed '1,3s/.*/Hello/' song.txt
OutputHello Hello Hello not knowing what it was and they'll continue singing it forever just because...
相反,您可以使用正则表达式仅匹配包含特定模式的行。 为此,请在给出命令字符串之前将匹配模式放在两个正斜杠 (/) 之间:
sed '/singing/s/it/& loudly/' song.txt
Outputthis is the song that never ends yes, it goes on and on, my friend some people started singing it loudly not knowing what it was and they'll continue singing it loudly forever just because...
在此示例中,您在包含字符串 singing
的每一行上第一次出现 it
之后放置了 loudly
。 请注意,第二行和第四行没有改变,因为它们与模式不匹配。
寻址表达式可以任意复杂。 这为执行命令提供了很大的灵活性。
这不是一个复杂的示例,但它演示了使用正则表达式为其他命令生成地址。 以下命令匹配任何空行(行首紧跟行尾)并将它们传递给删除命令:
sed '/^$/d' GPL-3
这是您将看到的输出:
Output GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for . . . . . .
请记住,您也可以在范围的任一侧使用正则表达式。 例如,您可以删除从仅包含单词 START
的行开始的行,直到读取为 END
的行,
例如,创建一个名为 inputfile
的文件:
echo "This is an input file START this is the text we don't want END This is additional text" > inputfile
现在使用 sed
删除 START
和 END
之间的内容:
sed '/^START$/,/^END$/d' inputfile
您将看到以下输出:
This is an input file This is additional text
不过需要注意的一点是,这将删除从第一个 START
到第一个 END
的所有内容,如果遇到另一个 START
标记,则重新开始删除。
如果要反转地址(在 不 与模式匹配的任何行上操作),可以在模式后面加上感叹号 (!
)。
例如,您可以使用以下命令删除任何 not 空白的行(不是非常有用,只是一个示例):
sed '/^$/!d' GPL-3
这会导致大量空白输出,因为 sed
默认情况下仍会打印行:
Output
地址不需要是要反转的复杂表达式。 反转在常规编号地址上也同样有效。
使用保持缓冲器
增加 sed
执行多行感知编辑能力的一项功能是所谓的“保持缓冲区”。 保持缓冲区是一个临时存储区域,可以通过某些命令进行修改。
这个额外缓冲区的存在意味着您可以在处理其他行的同时存储行,然后根据需要对每个缓冲区进行操作。
以下是影响保持缓冲区的命令:
- h:将当前模式缓冲区(您当前匹配并正在处理的行)复制到保持缓冲区(这会擦除保持缓冲区的先前内容)。
- H:将当前模式缓冲区追加到当前保持模式的末尾,由换行符 (\n) 分隔。
- g:将当前保持缓冲区复制到当前模式缓冲区中。 先前的模式缓冲区被擦除。
- G:将当前保持模式附加到当前模式缓冲区的末尾,由换行符 (\n) 分隔。
- x:交换当前模式和保持缓冲区。
在以一种或另一种方式将其移动到模式缓冲区之前,不能对保持缓冲区的内容进行操作。
让我们用一个复杂的例子来探讨这个想法。
这是一个如何连接相邻行的程序示例(sed
实际上有一个内置命令可以为我们处理很多事情。 N
命令将下一行附加到当前行。 不过为了练习,你会以艰难的方式做事):
sed -n '1~2h;2~2{H;g;s/\n/ /;p}' song.txt
这是您将看到的输出:
Outputthis is the song that never ends yes, it goes on and on, my friend some people started singing it not knowing what it was and they'll continue singing it forever just because...
这要消化很多,所以让我们分解一下。
首先要注意的是-n
选项是用来抑制自动打印的。 sed
只有在您明确告知时才会打印。
指令的第一部分是1\~2h
。 开头是地址规范,意思是在第一行执行后续操作,然后在之后的每隔一行(每个奇数行)执行后续操作。 h
部分是将匹配的行复制到保持缓冲区的命令。
命令的后半部分更复杂。 同样,它以地址规范开始。 这一次,它指的是偶数行(与第一个命令相反)。
该命令的其余部分用大括号括起来。 这意味着其余命令将继承刚刚指定的地址。 没有大括号,只有“H”命令会继承地址,其余命令将在每一行执行。
H
命令复制一个换行符,后跟当前模式缓冲区,到当前保持模式的末尾。
然后使用 g
命令将此保持模式(奇数行,后跟换行符,后跟偶数行)复制回模式缓冲区(替换先前的模式缓冲区)。
接下来,用空格替换换行符,并使用 p
命令打印该行。
如果你好奇,使用 N
命令会大大缩短这个时间。 以下命令将产生与您刚刚看到的相同的结果:
sed -n 'N;s/\n/ /p' song.txt
使用脚本
当您开始使用更复杂的命令时,在文本编辑器中编写它们可能会有所帮助。 如果您想将大量命令应用于单个目标,这也很有帮助。
例如,如果您喜欢以纯文本形式编写消息,但在使用文本之前需要执行一组标准化格式,则 sed
脚本会很有用。
您可以将命令放在脚本中并将其作为参数提供给 sed
,而不是键入每组 sed
调用。 sed
脚本只是原始 sed
命令的列表(通常在单引号字符之间的部分)。
要尝试此操作,请创建一个名为 sed_script
的新文件,其中包含以下内容:
echo "s/this/that/g s/people/horses/g 1,5s/it/that/g" > sed_script
保存文件并退出编辑器。
现在通过 -f
开关告诉 sed
使用该文件:
sed -f sed_script song.txt
结果将如下所示:
Outputthat is the song that never ends yes, that goes on and on, my friend some horses started singing that not knowing what that was and they'll continue singing that forever just because...
这允许您将所有编辑放在一个文件中,并在需要符合您创建的格式的任意文本文件上执行它。
结论
Sed 的命令一开始并不总是很容易理解,而且通常需要一些实际的实验才能了解它们的实用性。 因此,建议您在实际需要之前练习操作文本。 牢记最终目标并尝试仅使用 sed
来实现它。
希望到此为止,您已经开始了解正确掌握 sed
能给您带来的力量。 您对 sed
越满意,从长远来看,您需要做的工作就越少。