在 Linux 系统中,“文本处理三剑客”指的是三个核心命令行工具:
grep、sed和awk。它们各自分工明确,常配合使用,构成高效处理文本数据的基础能力,是每个运维人员的必备工具。本文将介绍 Linux 文本处理三剑客之文本编辑与转换利器——sed的一些理论知识。
[本文基于 CentOS Linux release 7 系统环境]
前情提要:通过往期文章《【Linux·基础篇】Shell 基础|常用命令·文本处理三剑客:grep》的内容,我们了解了 grep 的基本使用方法。
接下来,我将介绍第二位文本处理剑客——sed。
在 Linux 命令行下,文本处理是一项基本功。除了搜索,我们常常需要对文本进行批量替换、插入、删除等编辑操作。这时,我们就需要 sed(Stream Editor,流编辑器)了。
sed 不像普通编辑器那样需要交互式打开文件,而是像一条流水线,逐行读取文本,对模式空间(Pattern Space)中的内容进行处理后输出,可以帮助我们对文本更精确地操控,非常适合脚本化、自动化的场景。
【注】阅读须知
在介绍 sed 时,本文会涉及一些名称相近但含义不同的概念(如 sed 命令、 sed 命令名、 sed 脚本等)。我会在本文末“三、小结”一节中对这些概念统一进行详细介绍,建议在阅读正文前先浏览一遍,以避免混淆。初学者初次接触这些概念难免会有困惑,不必强求全部理解,当遇到困惑时随时温习即可。
sed:Stream Editor(流编辑器)
sed [OPTION]... {script-only-if-no-other-script} [input-file]...sed [选项]... {仅当没有其它脚本时的脚本} [输入文件]...sed 是一个非交互式的流编辑器,用于过滤和转换文本。它一次读取一行数据到模式空间,然后根据给定的 sed 脚本字符串中的 sed 命令对其内容进行处理,最后输出结果。
【注】模式空间 是 sed 用来存放当前正在处理的内容(通常是一行,也可能是多行)的工作缓冲区。绝大部分 sed 命令名(如 s、d、p 等)直接操作模式空间的内容,也有的 sed 命令名(如 a、r 等)基于它的内容来决定是否触发执行。
默认情况下,sed不会直接修改原文件,而是将处理后的结果打印到标准输出(除非使用 -i 选项直接原地修改文件)。
【注】有关标准输出的内容,可参考往期文章:
sed 的核心优势在于:无需打开文件即可完成批量、复杂的文本变换,且可以轻松嵌入到 Shell 脚本中。
在我们尝试使用 sed 前,需要先对 sed 的语法进行一些说明,这有助于我们对 sed 的使用方式有一个清楚的认识,以便后续快速上手。
【注】以下有些内容对初学者来说可能晦涩难懂,但请不要灰心,相信通过后续的学习和实际应用,一定会有所顿悟。建议初学者可以先大概浏览以下内容,等后续遇到相关问题时再重新温习。
sed 选项这里给出一些常用选项及其解释。
-n, --quiet, --silent解释:启用静默模式(禁止自动打印模式空间)。
【注】-n 的核心作用是抑制默认输出,只有 sed 命令中显式使用输出命令名(如 p)才会产生输出。
sed 调用不加 -n 时,sed 每处理一行都会将其输出到标准输出(通常是屏幕),这是默认行为;sed 调用加 -n 时,sed不再自动输出任何行,常显式使用命令名 p 打印匹配或指定的行,实现精准过滤。-i[后缀], --in-place[=后缀]解释:直接对文件进行原地编辑(若提供后缀则会先创建备份文件)。
【注】应谨慎使用 -i 选项,除非你知道自己在做什么!使用前建议先备份。其中,[后缀] 为可选的备份后缀。
后缀,则 sed 会直接覆盖原文件而不保留备份;后缀,则 sed 会先备份原文件(在原文件名后追加后缀作为备份文件名),再修改原文件。-e 脚本字符串, --expression=脚本字符串解释:将 sed 脚本字符串添加到待执行的 sed 脚本中。
【注】-e 的核心作用是允许在命令行上指定多个独立的 sed 脚本字符串。
sed 命令,则 -e可省略;sed 脚本,则可多次使用 -e。而有关 sed 脚本字符串的内容,可参考下文中的“说明 2:sed 脚本”一节。
-f 脚本文件, --file=脚本文件解释:将指定 sed 脚本文件的内容添加到待执行的 sed 脚本中。-f 可多次使用以加载多个 脚本文件。
【注】-f 的核心作用是允许我们在 脚本文件 里编写多个独立的 sed 命令,这适合处理复杂的批量文本操作,特别是当我们需要重复使用相同的脚本规则时。
-E, -r, --regexp-extended解释:在 sed 脚本中启用扩展正则表达式(ERE)。
【注】为最大可移植性,应尽量使用基本正则表达式(BRE)。若确需使用扩展正则表达式(ERE),建议优先使用 -E(符合较新的 POSIX 标准);-r 是 GNU 的传统选项,老系统可能只支持此选项,需根据目标环境选用。
而有关正则表达式的内容,可参考往期文章:
其中,有关基本正则表达式(BRE)和扩展正则表达式(ERE)的内容,我会补充在后续文章里,此处暂不做探讨。
sed 脚本sed 在实际使用中的命令格式在 sed 语法中,{script-only-if-no-other-script} 是一个带条件的可选参数占位符,它描述的是命令行的使用规则。
【注意】{script-only-if-no-other-script}不是让我们在命令行原样输入的字面字符串!即我们完全不需要在命令行里写出花括号或那段英文,它仅起提示作用。
这里的花括号是语法说明的标记,通常表示“在某些条件下可选”或者“有约束的可选部分”,而不是指正则表达式的大括号。
花括号里面的文字是条件说明。script-only-if-no-other-script 直译就是:仅当没有其它脚本时,才在此处给出脚本。
当然,这样的直译是令人困惑的,因此还是需要说明一下。实际上,我们在实际使用中常用的 sed 调用格式是:
sed [选项]... [脚本] [输入文件]...而 {script-only-if-no-other-script} 就对应中间的 [脚本] 部分(命令行中那个可选的位置参数)。它准确地告诉我们,[脚本] 这个位置提供的是一个 sed 脚本,但这是有条件的:
-e script、--expression=script、-f script-file 或 --file=script-file 来提供脚本;sed 脚本(以 sed 脚本字符串的形式);sed 脚本(以 sed 脚本字符串的形式)。简单来说,只有当没有用 选项 指定脚本时,才需要在 [脚本] 处给出那个 sed 脚本(以 sed 脚本字符串的形式)。
sed 脚本字符串sed 脚本字符串是一段指令文本,告诉 sed对哪些行做什么操作。这个字符串可以很简单,也可以很复杂。
一个 sed 脚本字符串里完全可以包含多条sed 命令,因为 sed 允许在同一对引号内用分号(;)或换行符来分隔命令。而每个 sed 命令又由一个可选的 地址限定(地址或地址范围,可后接 ! 取反修饰符)、后跟一个单字符 命令名 以及可选的 命令专属参数(任何附加的命令特定代码)组成。
具体来说,sed 命令的构成可以写成:
[地址限定][!]命令名[命令专属参数]【注】以上为语法结构示意;实际书写时,地址限定、! 与 命令名 之间通常不留空格,命令名 与 命令专属参数 之间是否留空格由具体的 命令名 决定。
sed 命令的构成:地址限定 —— 可选地址限定:可选,用于限定 sed 命令作用的行范围。它在 sed 命令中可省略不提供;若提供,则必须至少写一个地址,最多写两个地址(用逗号 , 分隔,构成地址范围)。
sed 命令在哪些行上执行。[地址1[,地址2]]sed 命令作用于所有行。sed 命令只作用于匹配该地址的行。sed 命令作用于从第一个地址匹配的行开始到第二个地址匹配的行结束的范围(闭区间,范围包含首尾)内的所有行。$:匹配最后一行;/regexp/,匹配内容符合模式的行;first~step,匹配从第 first 行开始的每 step 行。【注】在 sed 命令中 [地址限定] 的位置最多只能写两个地址(逗号分隔)。因为 sed 处理的是行的连续范围。一个范围只需要起点和终点两个端点,这就是“地址范围”的概念。语法上就是 地址1,地址2,再多写一个地址就没有意义了,sed 也会报错。
sed 命令的构成:!(取反)—— 可选!:取反修饰符,表示命令作用于不匹配地址限定(地址或地址范围)的行。若使用 !,则书写时必须紧挨 地址限定。
sed 命令作用于不匹配指定 地址限定 的行。[地址限定][!]【注】在 sed 命令中 ! 不是 地址限定 的一部分,而是用来反转地址限定 的匹配结果的修饰符,且必须依附于 地址限定 出现。如果没有任何 地址限定,自然也不能在 [地址限定] 位置之后,命令名 位置之前使用 !,因为没有匹配的 地址限定 可以“取反”。
sed 命令的构成:命令名 —— 必选命令名:必选,由一个单字符表示,决定操作类型。命令名 必须存在,否则 sed 命令就不成其为一条指令了。
sed 命令做什么操作。命令名地址限定 格式的不同,命令名 可分为零地址命令名、单地址命令名、接受地址范围的命令名。【注】根据 命令名 的类型分类,以下列出一些常用 命令名:
a:在行后添加文本;i:在行前插入文本;q:退出;r:读取文件并追加。d:删除;p:打印;s:替换;c:替换整行;y:字符转换;w:写入文件。sed 命令的构成:命令专属参数 —— 视 命令名 而定命令专属参数:位于 命令名 之后,是否必须提供及其书写格式,完全由具体的 命令名 决定。例如,若需要提供 命令专属参数,则部分 命令名 要求参数直接紧跟,不加空格;另一些 命令名 要求参数前必须加空格。
sed 命令提供必要的操作细节(目标、方式、范围等),使其能按预期执行。[命令专属参数]命令名 要求(是否含参)的不同,命令名 可以分成几类:必须带参数的命令名、不能带参数的命令名、可选参数的命令名。【注】根据 命令专属参数 的类型分类,以下列出一些常用 命令名:
a:在行后添加文本;i:在行前插入文本;s:替换;c:替换整行;y:字符转换;r:读取文件并追加;w:写入文件。d:删除;p:打印。q:退出。sed 的输入流可以是文件,也可以是来自管道的输入。例如:
sed [选项]... [脚本] [输入文件]...;command | sed [选项]... [脚本]。以上较多的是理论知识。通过这些理论,我们可以初步认识 sed,并了解其常用选项和使用格式。当然,只有理论还是远远不够的。在后续文章中,我会结合一些示例继续介绍 sed 的使用。
最后,为避免初学者困惑,我在这里对本文中一些容易混淆的概念进行澄清:
sed:指 sed 这个流编辑器工具本身,即“sed 程序”或“sed 工具”。sed 调用:指在命令行中执行 sed 的那一条完整的 Shell 命令,通常包括 sed 本身、选项、脚本以及输入文件。例如:sed -n -f script.sed file.txtsed 命令名:专指 sed 提供的单字符内置指令,决定了执行什么操作类型,是 sed 命令的核心。例如常见的命令名有 s(替换)、d(删除)、p(打印)、a(追加)、i(插入)、c(替换整行)、y(转写)、q(退出)、r(读取文件)、w(写入文件)等。sed 命令:指一条完整的处理指令,主要由三部分构成:可选的 地址限定、一个 命令名 和可选的 命令专属参数。例如:3,5s/foo/bar/g3,5:地址范围,表示只对输入文本的第 3 行到第 5 行(包含这两行)执行后续操作。s:作用是替换,基本格式为 s/旧内容/新内容/标志。/foo/bar/g:/foo/bar/:将每一行中出现的字符串 foo 替换为 bar。分隔符斜杠 / 也可以换成其它字符(如 #、|)。g:全局标志,表示对一行内所有匹配的 foo 都进行替换,而不是只替换第一个。如果没有 g,则每行只替换第一次出现的 foo。sed 脚本:一个逻辑概念,是由若干 sed 命令按顺序组成有序集合,代表一次 sed 调用的完整处理逻辑。例如:sed 's/foo/bar/; /bar/p; 5q' file.txt其中的 sed 脚本就是 s/foo/bar/、/bar/p、5q 三条 sed 命令的有序集合;
又如:
sed -e 's/foo/bar/' -e '/bar/p' -e '5q' file.txt其中的 sed 脚本同样是 s/foo/bar/、/bar/p、5q 这三条 sed 命令的有序集合。
【注】无论通过分号(;)还是多个 -e 选项构建,此处逻辑上形成的 sed 脚本完全一致。
s/foo/bar/:命令名 s 的作用是替换,该 sed 命令将当前行中第一次出现的 foo 替换为 bar(无 g 标志,所以每行只替换第一个匹配);并且,该 sed 命令对每一行都执行(因为没有指定地址限定)。/bar/p:/bar/ 是地址限定,命令名 p 的作用是打印。如果当前行经过上一步替换后包含字符串 bar,则将该行打印一次。由于 sed 默认会打印模式空间中的内容(即处理后的行),在未使用 -n 选项的情况下,使用 p 会导致匹配到的行被打印两次(一次来自默认输出,一次来自 p)。5q:5 是地址限定,命令名 q 的作用是退出。当处理到第 5 行(无论是否被替换或打印)时,立即停止读取后续行并退出 sed。这意味着第 5 行之后的所有行都不会被处理或输出。sed 脚本字符串:直接写在命令行中、用引号括起来的 sed 命令文本,是一种临时、内联的文本形式。一个 sed 脚本字符串可以只包含一条 sed 命令,也可以包含多条 sed 命令(用分号 ; 分隔)。例如,在 sed 脚本的举例中:
无论哪种方式,它们构成的 sed 脚本完全相同。
's/foo/bar/; /bar/p; 5q' 是一个sed 脚本字符串(内含三条 sed 命令);-e 's/foo/bar/'、-e '/bar/p'、-e '5q' 是三个独立的 sed 脚本字符串(每个内含一条 sed 命令)。sed 脚本文件:保存在磁盘上的文本文件(通常以 .sed 为后缀),内容是若干 sed 命令文本,由 -f 选项加载,是一种持久、外部的文本形式(便于复用和管理)。例如,调用 sed -f script.sed file.txt,其中 script.sed 的内容可以是:# 将 foo 替换为 bars/foo/bar/# 打印包含 bar 的行/bar/p# 处理到第 5 行后退出5q由此我们也可以看出,“sed 脚本字符串”和“sed 脚本文件”都是 sed 命令的文本形式,二者功能完全相同,区别仅在于存在形式——一个是命令行里的临时字符串,一个是文件里的持久化内容。
此番炼器手札,炉火尚未全熄。若道友观之有趣,或可暂留此间,结一尘缘。待下回开炉铸器,新得感悟,必先与同道分享。