sed — 流编辑器

有时使用正则表达式来操作内容比修补源代码更好。这可以用于小的更改,特别是那些可能在不同版本之间产生补丁冲突的更改。执行此操作的规范方法是通过 sed

# This plugin is mapped to the 'h' key by default, which conflicts with some
# other mappings. Change it to use 'H' instead.
sed -i 's/\(noremap <buffer> \)h/\1H/' info.vim \
	|| die 'sed failed'

另一个常见的例子是追加一个 -gentoo-blah 版本字符串(一些上游希望我们这样做,以便他们能够准确地知道他们正在处理哪个软件包)。同样,我们可以使用 sed。请注意,如果我们的版本中没有 -r 组件,则 ${PR} 变量将被设置为 r0

# Add in the Gentoo -r number to fluxbox -version output. We need to look
# for the line in version.h.in which contains "__fluxbox_version" and append
# our content to it.
if [[ "${PR}" == "r0" ]] ; then
	suffix="gentoo"
else
	suffix="gentoo-${PR}"
fi
sed -i \
    -e "s~\(__fluxbox_version .@VERSION@\)~\1-${suffix}~" \
    version.h.in || die "version sed failed"

还可以通过这种方式从现有文件中提取内容以创建新文件。许多 app-vim ebuild 使用此技术从插件文件中提取文档并将其转换为 Vim 帮助格式。

# This plugin uses an 'automatic HelpExtractor' variant. This causes
# problems for us during the unmerge. Fortunately, sed can fix this
# for us. First, we extract the documentation:
sed -e '1,/^" HelpExtractorDoc:$/d' \
	"${S}"/plugin/ZoomWin.vim > ${S}/doc/ZoomWin.txt \
	|| die "help extraction failed"
# Then we remove the help extraction code from the plugin file:
sed -i -e '/^" HelpExtractor:$/,$d' "${S}"/plugin/ZoomWin.vim \
	|| die "help extract remove failed"

以下是使用 sed 的一些更常见方法的总结以及常用地址和标记模式的描述。请注意,其中一些结构特定于 GNU sed 4 — 在非 GNU 用户空间架构上,必须将 sed 命令设为 GNU sed 的别名。另请注意,GNU sed 4 作为 @system 的一部分保证已安装。情况并非总是如此,这就是为什么某些软件包,特别是那些使用 sed -i 的软件包,依赖于 >=sys-apps/sed-4 的原因。

基本的 sed 调用

调用的基本形式是

sed [ option flags ] \
	-e 'first command' \
	-e 'second command' \
	-e 'and so on' \
	input-file > output-file \
	|| die "Oops, sed didn't work!"

对于输入和输出文件相同的情况,应使用就地选项。这是通过将 -i 作为选项标志之一来完成的。

通常 sed 会打印出创建内容的每一行。要仅获取显式打印的行,应使用 -n 标志。

使用 sed 进行简单的文本替换

sed 最常见的形式是将所有 some text 的实例替换为 different content。操作如下

# replace all instances of "some text" with "different content" in
# somefile.txt
sed -i -e 's/some text/different content/g' somefile.txt || \
	die "Sed broke!"

如果模式或替换字符串包含正斜杠字符,通常最简单的方法是使用不同的分隔符。大多数标点符号都是允许的,尽管应避免使用反斜杠和任何形式的括号。您应小心选择分隔符,以确保它不会出现在主题/替换中涉及的任何字符串中。例如,将 sed 与 CFLAGS 一起使用很危险,因为它是由用户提供的数据(因此可能包含任何字符),但人们尤其应该避免例如 冒号

# replace all instances of "/usr/local" with "/usr"
sed -i -e 's~/usr/local~/usr~g' somefile.txt || \
	die "sed broke"

可以通过使用 ^$ 元字符来使模式仅在行的开头或结尾匹配。^ 表示“仅在行的开头匹配”,$ 表示“仅在行的结尾匹配”。通过在一个语句中同时使用两者,可以匹配精确的行。

# Replace any "hello"s which occur at the start of a line with "howdy".
sed -i -e 's!^hello!howdy!' data.in || die "sed failed"
# Replace any "bye"s which occur at the end of a line with "cheerio!".
sed -i -e 's,bye$,cheerio!,' data.in || die "sed failed"
# Replace any lines which are exactly "change this line" with "have a
# cookie".
sed -i -e 's-^change this line$-have a cookie-' data.in || die "Oops"

要在模式中忽略大小写,请添加 /i 标志。

# Replace any "emacs" instances (ignoring case) with "Vim"
sed -i -e 's/emacs/Vim/gi' editors.txt || die "Ouch"

使用 sed 进行正则表达式替换

也可以使用 sed 进行更复杂的匹配。一些示例可能包括

  • 匹配任何三位数字
  • 匹配“foo”或“bar”
  • 匹配任何字母“a”、“e”、“i”、“o”或“u”

这些类型的模式可以链接在一起,从而导致诸如“匹配任何元音后跟两位数字,然后后跟 foo 或 bar”之类的事情。

要匹配一组字符中的任何一个,可以使用字符类。它们有三种形式。

  • 反斜杠后跟一个字母。例如,\d 匹配单个数字(0、1、2、... 9 中的任何一个)。\s 匹配单个空格字符。稍后将在本文档中提供更有用类的表格。
  • 方括号内的字符组。例如,[aeiou] 匹配 'a'、'e'、'i'、'o' 或 'u' 中的任何一个。允许使用范围,例如 [0-9A-Fa-fxX],它可以用于匹配任何十六进制数字或字符 'x' 和 'X'。反向字符类,例如 [^aeiou],匹配除了列出的字符之外的任何单个字符。
  • POSIX 字符类是一组特殊的命名字符组,它们是特定于区域设置的。例如,[[:alpha:]] 匹配当前区域设置中的任何“字母”字符。稍后将在本文档中提供更有用类的表格。

要匹配多个选项中的任何一个,可以使用替换。基本形式是 first\|second\|third

要对项目进行分组以避免歧义,可以使用 \(parentheses\) 结构。要匹配“iniquity”或“infinity”,可以使用 in\(iqui\|fini\)ty

要选择性地匹配项目,请在项目后面添加 \?。例如,colou\?r 匹配“colour”和“color”。这也可以应用于字符类和括号内的组,例如 \(in\)\?finite\(ly\)\?。还有其他原子可用于匹配“一个或多个”、“零个或多个”、“至少 n 个”、“n 到 m 个”等等——这些将在本文档后面总结。

在替换命令的替换部分中也可以使用一些特殊结构。要插入模式的第一个匹配括号组的内容,请使用 \1,对于第二个使用 \2,依此类推,直到 \9。可以使用未转义的与号 & 字符插入匹配的整个内容。这些和其他替换原子将在本文档后面总结。

sed 中的地址

许多 sed 命令只能应用于特定行或行范围。如果只想对文档的前十行进行操作,这可能很有用。

地址最简单的形式是一个正整数。这将导致仅对相关行应用以下命令。行号从 1 开始,但当希望在第一行之前插入文本时,地址 0 可能很有用。如果在 50 行的文档上使用地址 100,则关联的命令将永远不会执行。

要匹配文档中的最后一行,可以使用 $ 地址。

要匹配与给定正则表达式匹配的任何行,可以使用 /pattern/ 形式。这对于查找特定行并对其进行某些更改很有用——有时分两个阶段处理比使用一个大的可怕的 s/// 命令更简单。在范围内使用时,它可以用于查找两个给定标记之间或给定标记和文档末尾之间的所有文本。

要匹配一系列地址,可以使用 addr1,addr2。大多数地址结构都允许用于起始和结束地址。

地址可以使用感叹号取反。要匹配除最后一行之外的所有行,可以使用 $!

最后,如果命令没有给出地址,则该命令将应用于输入中的每一行。

还有其他涉及地址链接的更复杂选项可用。本文档中未讨论这些选项。

使用 sed 删除内容

可以使用 address d 命令从文件中删除行。要删除文件的第三行,可以使用 3d,要过滤掉所有包含“fred”的行,可以使用 /fred/d

使用 sed 提取内容

当将 -n 选项传递给 sed 时,默认情况下不会打印任何输出。可以使用 p 命令显示内容。例如,要打印包含“infra monkey”的行,可以使用命令 sed -n -e '/infra monkey/p'。还可以打印范围——sed -n -e '/^START$/,/^END$/p' 有时很有用。

使用 sed 插入内容

要使用 sed 插入文本,请使用 address ai 命令。a 命令在匹配行的下一行插入,而 i 命令在匹配行的上一行插入。

像往常一样,地址可以是行号或正则表达式:行号命令只执行一次,而正则表达式插入/追加将为每个匹配执行。

# Add 'Bob' after the 'To:' line:
sed -i -e '/^To: $/a    Bob' data.in || die "Oops"

# Add 'From: Alice' before the 'To:' line:
sed -i -e '/^To: $/i    From: Alice'

# Note that the spacing between the 'i' or 'a' and 'Bob' or 'From: Alice' is simply ignored'

# Add 'From: Alice' indented by two spaces: (You only need to escape the first space)
sed -i -e '/^To: $/i\  From: Alice'

请注意,应尽可能使用匹配而不是行号。例如,这可以减少在文件开头添加一行时导致 sed 脚本中断的问题。

sed 中的正则表达式原子

基本原子

原子 用途
text 文字文本
\( \) 分组
\| 交替,a b
* \? \+ \{\} 重复,见下文
. 任何单个字符
^ 行首
$ 行尾
[abc0-9] 任意一个
[^abc0-9] 除……之外的任何一个字符
[[:alpha:]] POSIX 字符类,见下文
\1 .. \9 反向引用
\x(任何特殊字符) 按字面意思匹配字符
\x(普通字符) 快捷方式,见下文

字符类快捷方式

原子 描述
\a "BEL" 字符
\f "换页" 字符
\t "制表符" 字符
\w "单词"(字母、数字或下划线)字符
\W "非单词" 字符

POSIX 字符类

阅读源代码,这是唯一正确记录这些内容的地方……

描述
[[:alpha:]] 字母字符
[[:upper:]] 大写字母
[[:lower:]] 小写字母
[[:digit:]] 数字
[[:alnum:]] 字母和数字字符
[[:xdigit:]] 十六进制数字中允许的数字
[[:space:]] 空格字符
[[:print:]] 可打印字符
[[:punct:]] 标点符号字符
[[:graph:]] 非空白字符
[[:cntrl:]] 控制字符

计数说明符

原子 描述
* 零个或多个(贪婪)
\+ 一个或多个(贪婪)
\? 零个或一个(贪婪)
\{N\} 正好 N 个
\{N,M\} 至少 N 个,最多 M 个(贪婪)
\{N,\} 至少 N 个(贪婪)

sed 中的替换原子

原子 描述
\1 .. \9 捕获的 \( \) 内容
& 整个匹配文本
\L 所有后续字符都转换为小写
\l 以下字符转换为小写
\U 所有后续字符都转换为大写
\u 以下字符转换为大写
\E 取消最近的 \L\U

sed 匹配机制的详细信息

GNU sed 使用传统的(非 POSIX)非确定性有限自动机以及扩展来支持捕获以进行匹配。这意味着在所有情况下,都将优先考虑最左边的起始位置的匹配。在所有最左边的可能匹配中,将优先考虑最左边的交替选项。最后,在所有其他条件相同的情况下,将优先考虑最左边的计数选项中最长的那个。

其中大部分违反了严格的 POSIX 兼容性,因此最好不要依赖它。可以安全地假设 sed 将始终选择最左边的匹配,并且它将以贪婪的方式进行匹配,并优先考虑模式中较早的项目。

关于 sed 性能的说明

对于希望进一步了解正则表达式的读者,作者推荐 Jeffrey E. F. Friedl 编著的《精通正则表达式》。此文本非常缺乏诸如“令 t 为一个有限的连续序列,使得 t[n] ∈ ∑ ∀ n”之类的短语,并且并非由其薪水依赖于能够用几页数学和希腊符号来表达简单概念的人撰写。