如何在 Linux 上使用 sed 命令

这听起来可能很疯狂,但 Linux sed
命令是一个没有界面的文本编辑器。您可以从命令行使用它来操作文件和流中的文本。我们将向您展示如何利用它的力量。
sed 的力量
sed
命令有点像国际象棋:学习基础知识需要一个小时,掌握它们需要一生(或者,至少需要大量练习)。我们将向您展示 sed
功能的每个主要类别中的一些开场白。
sed
是一种流编辑器,适用于管道输入或文本文件。但是,它没有交互式文本编辑器界面。相反,您提供说明以使其在文本中运行时遵循。这一切都适用于 Bash 和其他命令行 shell。
使用 sed
,您可以执行以下所有操作:
- 选择文本
- 替换文本
- 在文本中添加行
- 从文本中删除行
- 修改(或保留)原始文件
我们构建示例来介绍和演示概念,而不是生成最简洁(且最不易于理解)的 sed
命令。但是,sed
的模式匹配和文本选择功能在很大程度上依赖于正则表达式 (regexes)。您需要熟悉这些才能充分利用 sed
。
一个简单的例子
首先,我们将使用 echo
通过管道将一些文本发送到 sed
,并让 sed
替换部分文本。为此,我们键入以下内容:
echo howtogonk | sed 's/gonk/geek/'
echo
命令将“howtogonk”发送到 sed
,并应用我们的简单替换规则(“s”代表替换)。 sed
在输入文本中搜索第一个字符串的出现,并将用第二个字符串替换任何匹配项。
字符串“gonk”被替换为“geek”,新字符串打印在终端窗口中。

替换可能是 sed
最常见的用法。不过,在我们深入研究替换之前,我们需要知道如何选择和匹配文本。
选择文本
我们的示例需要一个文本文件。我们将使用一个包含塞缪尔·泰勒·柯勒律治史诗《古舟子咏》中节选的诗句。
我们键入以下内容以使用 less
查看它:
less coleridge.txt

为了从文件中选择一些行,我们提供了我们想要选择的范围的开始和结束行。一个数字选择那一行。
要提取第一到第四行,我们键入以下命令:
sed -n '1,4p' coleridge.txt
请注意 1
和 4
之间的逗号。 p
表示“打印匹配的行”。默认情况下,sed
打印所有行。我们会看到文件中的所有文本都打印了两次匹配行。为防止这种情况,我们将使用 -n
(安静)选项来抑制不匹配的文本。
我们更改行号,以便我们可以选择不同的经文,如下所示:
sed -n '6,9p' coleridge.txt

我们可以使用 -e
(表达式)选项进行多项选择。使用两个表达式,我们可以选择两个经文,如下所示:
sed -n -e '1,4p' -e '31,34p' coleridge.txt
如果我们减少第二个表达式中的第一个数字,我们可以在两节经文之间插入一个空格。我们键入以下内容:
sed -n -e '1,4p' -e '30,34p' coleridge.txt

我们还可以选择起始行并告诉 sed
单步执行文件并打印交替行,每五行打印一次,或者跳过任意数量的行。该命令类似于我们上面用来选择范围的命令。然而,这一次,我们将使用波浪号 (~
) 而不是逗号来分隔数字。
第一个数字表示起始行。第二个数字告诉 sed
我们希望看到起始行之后的哪些行。数字 2 表示每隔两行,3 表示每隔三行,依此类推。
我们键入以下内容:
sed -n '1~2p' coleridge.txt

您不会总是知道您要查找的文本在文件中的位置,这意味着行号并不总是有很大帮助。但是,您也可以使用 sed
选择包含匹配文本模式的行。例如,让我们提取所有以“And”开头的行。
插入符号 (^
) 代表行的开始。我们会将搜索词括在正斜杠 (/
) 中。我们还在“And”之后添加了一个空格,这样像“Android”这样的词就不会包含在结果中。
一开始阅读 sed
脚本可能有点困难。 /p
的意思是“打印”,就像我们上面使用的命令一样。但是,在以下命令中,它前面有一个正斜杠:
sed -n '/^And /p' coleridge.txt

从文件中提取并显示以“And”开头的三行。
换人
在我们的第一个示例中,我们向您展示了以下用于 sed
替换的基本格式:
echo howtogonk | sed 's/gonk/geek/'
s
告诉 sed
这是一个替换。第一个字符串是搜索模式,第二个是我们要用来替换匹配文本的文本。当然,与 Linux 的所有事物一样,细节决定成败。
我们输入以下内容,将所有出现的“day”更改为“week”,让水手和信天翁有更多时间建立联系:
sed -n 's/day/week/p' coleridge.txt
在第一行中,仅更改了第二次出现的“day”。这是因为 sed
在每行第一次匹配后停止。我们必须在表达式的末尾添加一个“g”,如下所示,以执行全局搜索,以便处理每一行中的所有匹配项:
sed -n 's/day/week/gp' coleridge.txt
这匹配第一行中四个中的三个。因为第一个词是“Day”,而 sed
区分大小写,所以它不会将该实例视为与“day”相同。
我们键入以下内容,在表达式末尾的命令中添加一个 i
以指示不区分大小写:
sed -n 's/day/week/gip' coleridge.txt
这可行,但您可能并不总是想为所有内容打开不区分大小写。在这些情况下,您可以使用正则表达式组来添加特定于模式的不区分大小写。
例如,如果我们将字符括在方括号 ([]
) 中,它们将被解释为“此字符列表中的任何字符”。
我们键入以下内容,并在组中包含“D”和“d”,以确保它同时匹配“Day”和“day”:
sed -n 's/[Dd]ay/week/gp' coleridge.txt

我们还可以限制对文件部分的替换。假设我们的文件在第一节中包含奇怪的间距。我们可以使用以下熟悉的命令来查看第一节:
sed -n '1,4p' coleridge.txt
我们将搜索两个空格并将它们替换为一个。我们将在全球范围内执行此操作,以便在整条线上重复该操作。要清楚,搜索模式是空格,空格星号(*
),替换字符串是单个空格。 1,4
将替换限制为文件的前四行。
我们将所有这些放在以下命令中:
sed -n '1,4 s/ */ /gp' coleridge.txt
这很好用!搜索模式在这里很重要。星号 (*
) 代表零个或多个前面的字符,这是一个空格。因此,搜索模式正在寻找一个或多个空格的字符串。
如果我们用一个空格替换任何多个空格的序列,我们将把文件恢复为常规间距,每个单词之间只有一个空格。在某些情况下,这也会用一个空格代替一个空格,但这不会产生任何不利影响——我们仍然会得到我们想要的结果。
如果我们键入以下内容并将搜索模式缩减为一个空格,您会立即明白为什么我们必须包含两个空格:
sed -n '1,4 s/ */ /gp' coleridge.txt

因为星号匹配零个或多个前面的字符,它会将每个不是空格的字符视为“零空格”并对其应用替换。
但是,如果我们在搜索模式中包含两个空格,sed
必须在应用替换之前找到至少一个空格字符。这确保非空格字符将保持不变。
我们使用之前使用的 -e
(表达式)键入以下内容,这允许我们同时进行两个或多个替换:
sed -n -e 's/motion/flutter/gip' -e 's/ocean/gutter/gip' coleridge.txt
如果我们使用分号 (;
) 分隔两个表达式,我们可以获得相同的结果,如下所示:
sed -n 's/motion/flutter/gip;s/ocean/gutter/gip' coleridge.txt

当我们在以下命令中将“day”替换为“week”时,表达式“well a-day”中的“day”实例也会被替换:
sed -n 's/[Dd]ay/week/gp' coleridge.txt
为了防止这种情况,我们只能尝试在匹配另一个模式的行上进行替换。如果我们修改命令以在开头具有搜索模式,我们将只考虑在与该模式匹配的行上进行操作。
我们输入以下内容使我们的匹配模式成为单词“after”:
sed -n '/after/ s/[Dd]ay/week/gp' coleridge.txt
这给了我们想要的回应。

更复杂的替换
让我们让 Coleridge 休息一下,使用 sed
从 etc/passwd
文件中提取名称。
有更短的方法可以做到这一点(稍后会详细介绍),但我们将在这里使用更长的方法来演示另一个概念。搜索模式(称为子表达式)中的每个匹配项目都可以编号(最多九个项目)。然后,您可以在 sed
命令中使用这些数字来引用特定的子表达式。
您必须将子表达式括在括号 [()
] 中才能使其工作。圆括号前还必须有一个反斜杠 (\
),以防止它们被视为普通字符。
为此,您将键入以下内容:
sed 's/\([^:]*\).*/\1/' /etc/passwd

让我们分解一下:
sed s/
:sed
命令和替换表达式的开头。\(
: 左括号 [(
] 括起子表达式,前面是反斜杠 (\
).[^:]*
: 搜索词的第一个子表达式包含方括号中的组。插入符号 (^
) 在组中使用时表示“不”。组意味着任何不是冒号的字符 (:
) 都将被接受为匹配项。\)
:右括号 [)
] 前面有反斜杠 (\
)。李>.*
:第二个搜索子表达式表示“任何字符和任意数量的字符。”/\1
:表达式的替换部分包含1
,前面是反斜杠 (\
) .这表示与第一个子表达式匹配的文本。/
: 结束正斜杠 (/
) 和单引号 () 终止
sed
命令。
这一切意味着我们将寻找任何不包含冒号的字符串 (:
),这将是匹配文本的第一个实例。然后,我们在该行搜索任何其他内容,这将是匹配文本的第二个实例。我们将用与第一个子表达式匹配的文本替换整行。
/etc/passwd
文件中的每一行都以冒号结尾的用户名开头。我们将所有内容匹配到第一个冒号,然后用该值替换整行。所以,我们已经隔离了用户名。

接下来,我们将第二个子表达式括在括号 [()
] 中,这样我们也可以通过数字引用它。我们还将 \1
替换为 \2
。我们的命令现在将用从第一个冒号 (:
) 到行尾的所有内容替换整行。
我们键入以下内容:
sed 's/\([^:]*\)\(.*\)/\2/' /etc/passwd

这些小改动颠倒了命令的含义,我们得到了除用户名之外的所有内容。

现在,让我们看一下执行此操作的快速简便方法。
我们的搜索词是从第一个冒号 (:
) 到行尾。因为我们的替换表达式是空的 (//
),所以我们不会用任何东西替换匹配的文本。
因此,我们键入以下内容,从第一个冒号 (:
) 到行尾的所有内容都被删除,只留下用户名:
sed 's/:.*//" /etc/passwd

让我们看一个示例,其中我们在同一命令中引用了第一个和第二个匹配项。
我们有一个用逗号 (,
) 分隔名字和姓氏的文件。我们想将它们列为“姓氏,名字”。我们可以使用 cat
来查看文件中的内容,如下所示:
cat geeks.txt
与许多 sed
命令一样,下一个命令乍一看可能难以理解:
sed 's/^\(.*\),\(.*\)$/\2,\1 /g' geeks.txt

这是一个替换命令,就像我们使用的其他命令一样,搜索模式非常简单。我们将在下面对其进行分解:
sed s/
:普通替换命令。^
:因为插入符号不在组中 ([]
),所以它表示“行的开头”。\(.*\),
: 第一个子表达式是任意数量的任意字符。它被括在括号 [()
] 中,每个括号前面都有一个反斜杠 (\
),因此我们可以通过数字引用它。到目前为止,我们的整个搜索模式转换为从行首到第一个逗号 (,
) 的任意数量任意字符的搜索。\(.*\)
:下一个子表达式(同样)是任意数量的任意字符。它还包含在圆括号 [()
] 中,两个圆括号前面都有一个反斜杠 (\
),因此我们可以按编号引用匹配的文本。$/
: 美元符号 ($
) 代表行尾,将使我们的搜索继续到行尾线。我们只是用它来介绍美元符号。我们在这里并不真的需要它,因为星号 (*
) 在这种情况下会转到行尾。正斜杠 (/
) 完成搜索模式部分。\2,\1 /g
:因为我们将两个子表达式括在括号中,所以我们可以通过它们的编号来引用它们。因为我们想颠倒顺序,所以我们将它们键入second-match,first-match
。数字前面必须有反斜杠 (\
)。/g
:这使我们的命令能够在每一行上全局工作。geeks.txt
:我们正在处理的文件。
您还可以使用剪切命令 (c
) 替换与您的搜索模式匹配的整行。我们键入以下内容以搜索其中包含单词“neck”的行,并将其替换为新的文本字符串:
sed '/neck/c Around my wrist was strung' coleridge.txt

我们的新行现在出现在我们摘录的底部。

插入线条和文本
我们还可以在文件中插入新行和文本。要在任何匹配行之后插入新行,我们将使用追加命令 (a
)。
这是我们要使用的文件:
猫极客.txt

我们已经对行进行了编号,以使其更容易理解。
我们键入以下内容来搜索包含单词“He”的行,并在它们下面插入一个新行:
sed '/He/a --> Inserted!' geeks.txt

我们键入以下内容并包含插入命令 (i
) 以在包含匹配文本的行上方插入新行:
sed '/He/i --> Inserted!' geeks.txt

我们可以使用表示原始匹配文本的符号 (&
) 将新文本添加到匹配行。 \1
、\2
等表示匹配的子表达式。
要将文本添加到行的开头,我们将使用匹配行中所有内容的替换命令,并结合将新文本与原始行组合的替换子句。
为此,我们输入以下内容:
sed 's/.*/--> Inserted &/' geeks.txt

我们键入以下内容,包括 G
命令,这将在每行之间添加一个空行:
sed 'G' geeks.txt

如果要添加两个或多个空行,可以使用 G;G
、G;G;G
等。
删除线
Delete 命令 (d
) 删除与搜索模式匹配的行,或者用行号或范围指定的行。
例如,要删除第三行,我们将键入以下内容:
sed '3d' geeks.txt
要删除第四到第五行的范围,我们将键入以下内容:
sed '4,5d' geeks.txt
要删除范围外的行,我们使用感叹号 (!
),如下所示:
sed '6,7!d' geeks.txt

保存您的更改
到目前为止,我们所有的结果都已打印到终端窗口,但我们还没有将它们保存在任何地方。要使这些永久化,您可以将更改写入原始文件或将它们重定向到新文件。
覆盖原始文件需要谨慎。如果您的 sed
命令错误,您可能会对原始文件进行一些难以撤消的更改。
为了让您高枕无忧,sed
可以在执行命令之前创建原始文件的备份。
您可以使用就地选项 (-i
) 告诉 sed
将更改写入原始文件,但是如果您向其添加文件扩展名,sed
会将原始文件备份到新文件。它将具有与原始文件相同的名称,但具有新的文件扩展名。
为了演示,我们将搜索任何包含单词“He”的行并将其删除。我们还将使用 BAK 扩展名将原始文件备份到一个新文件。
为此,我们输入以下内容:
sed -i'.bak' '/^.*He.*$/d' geeks.txt
我们键入以下内容以确保我们的备份文件未更改:
cat geeks.txt.bak

我们还可以键入以下内容将输出重定向到一个新文件并获得类似的结果:
sed -i'.bak' '/^.*He.*$/d' geeks.txt > new_geeks.txt
我们使用 cat
来确认更改已写入新文件,如下所示:
cat new_geeks.txt

已经 sed All That
您可能已经注意到,即使是这个关于 sed
的快速入门也很长。这个命令有很多功能,你可以用它做更多的事情。
不过,希望这些基本概念为您打下了坚实的基础,您可以在此基础上继续学习更多内容。
Linux Commands | ||
Files | tar · pv · cat · tac · chmod · grep · diff · sed · ar · man · pushd · popd · fsck · testdisk · seq · fd · pandoc · cd · $PATH · awk · join · jq · fold · uniq · journalctl · tail · stat · ls · fstab · echo · less · chgrp · chown · rev · look · strings · type · rename · zip · unzip · mount · umount · install · fdisk · mkfs · rm · rmdir · rsync · df · gpg · vi · nano · mkdir · du · ln · patch · convert · rclone · shred · srm · scp · gzip · chattr · cut · find · umask · wc | |
Processes | alias · screen · top · nice · renice · progress · strace · systemd · tmux · chsh · history · at · batch · free · which · dmesg · chfn · usermod · ps · chroot · xargs · tty · pinky · lsof · vmstat · timeout · wall · yes · kill · sleep · sudo · su · time · groupadd · usermod · groups · lshw · shutdown · reboot · halt · poweroff · passwd · lscpu · crontab · date · bg · fg · pidof · nohup · pmap | |
Networking | netstat · ping · traceroute · ip · ss · whois · fail2ban · bmon · dig · finger · nmap · ftp · curl · wget · who · whoami · w · iptables · ssh-keygen · ufw · arping · firewalld |
RELATED: Best Linux Laptops for Developers and Enthusiasts