自 60 年代末以来,Sed 就是 Unix 标准工具箱的一部分。 作为任何文本编辑器,它将帮助您修改文本文件。 但是,与您可能已经使用过的文本编辑器相反,这是一种非交互式的。
这意味着您提前指定要应用于文件的转换,然后该工具可以在无人监督的情况下应用这些转换。
该工具的设计目标的最佳描述来自 李·E·麦克马洪,他的原始实现的核心开发者 sed原纸:
Sed 是在 UNIX 操作系统上运行的非交互式上下文编辑器。 Sed 旨在在以下三种情况下特别有用:
- 编辑太大而无法进行舒适的交互式编辑的文件;
- 当编辑命令序列过于复杂而无法在交互模式下舒适地键入时,可以编辑任何大小的文件。
- 通过输入一次有效地执行多个“全局”编辑功能。
目标设计 (1) 和 (3) 可能与我们的现代硬件不太相关,但第二个仍然有效。 作为个人补充,我想说 sed 特别适合重复性任务,例如当您想对一组文件应用相同的转换时。
通过这些示例学习基本的 SED 命令
为了让您了解 sed 背后的强大功能,我将考虑一个开发人员需要在其项目中的每个源文件之上添加许可证头的情况:
[email protected]:~$ head MIT.LICENSE *.sh
==> MIT.LICENSE <==
-----8<----------------------------------------------------------------
Copyright <YEAR> <COPYRIGHT HOLDER>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
==> script1.sh <==
#!/bin/bash
echo Hello, I'm the first script
==> script2.sh <==
#!/bin/bash
cat << EOF
Hello, I'm the second script
EOF
我不仅希望看到每个 shell 脚本顶部的许可证文件,而且还希望将年份和版权占位符替换为它们的实际值。 这将是我们的第一个用例。
注意:如果你想自己练习,你可以 从我的网站下载示例文件. 您可能还想观看完成本文的视频:
1. 替换 SED 中的文本
在我的许可证文件中,我想将
这是一个非常适合 sed 替换命令的工作。 可能是所有 sed 命令中最有用的:
[email protected]:~$ sed -e 's/<YEAR>/2018/' MIT.LICENSE | head -5
-----8<----------------------------------------------------------------
Copyright 2018 <COPYRIGHT HOLDER>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
使用管道 (|
),我将 sed 命令的输出转发到 head
工具在这里只显示前五行。 但是,对于我们今天的具体话题,最有趣的部分是 s/<YEAR>/2018/
表达。
Sed 通过一次处理一行输入文件来工作。 在每一行,替代品 (s
) 命令将替换前两个斜杠 (/<YEAR>/
) 通过最后两个之间的文本 (/2018/
)。 可以把它想象成 GUI 文本编辑器中的搜索替换功能。
这里值得一提的是,原始的 MIT.LICENSE 文件没有被修改。 我让您使用以下命令自行检查:
head -5 MIT.LICENSE
2. 再次替换文字……
太好了:我们已经替换了年份占位符。 但是还有第二个可以替换。 如果你理解前面的 example,您可能可以想象第二个 sed 表达式,如下所示:
's/<COPYRIGHT HOLDER>/Sylvain Leroux/'
但是该放在哪里呢? 好吧,你有几个选择。 如果您已经熟悉重定向的概念,最明显的方法是将我们第一个 sed 命令的输出通过管道传输到第二个 sed 实例:
[email protected]:~$ sed -e 's/<YEAR>/2018/' MIT.LICENSE |
sed -e 's/<COPYRIGHT HOLDER>/Sylvain Leroux/' |
head -5
----8<----------------------------------------------------------------
Copyright 2018 Sylvain Leroux
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
但我们可以做得更好。 由于 -e
option 引入了一个 sed 表达式,我们可以将它们中的几个用作同一个 sed 调用的一部分,结果将是相同的:
# Pay special attention to the at the end of the lines
# specifying the *same* command continues on the
# next line:
sh$ sed -e 's/<YEAR>/2018/'
-e 's/<COPYRIGHT HOLDER>/Sylvain Leroux/'
MIT.LICENSE |
head -5
最后,您还可以在同一个 sed 表达式中指定多个命令,方法是用新行分隔它们。 这在您开始编写更复杂的 sed 程序时特别有用:
# Pay special attention to the single-quotes and
# backslash placement:
sh$ sed -e 's/<YEAR>/2018/
s/<COPYRIGHT HOLDER>/Sylvain Leroux/'
MIT.LICENSE |
head -5
3. 插入文字
现在我们已经用它们的实际值替换了占位符。 但是在能够将该许可证文件插入项目文件之前,我们还有一些工作要做。 那些后来成为 shell 脚本的,许可证的每一行都必须以 章鱼 (#
) 为了让 shell 理解它不应该尝试解释这些行。
为此,我们将再次使用替换命令。 我之前没有提到的是,与 GUI 编辑器的大多数搜索替换功能相反,搜索模式不一定是要搜索的文字字符串。 其实这是一个 正则表达式(正则表达式). 这意味着,除了将逐字匹配的普通字符外,您还可以使用具有特殊含义的字符。 为了 example插入符号 (^
) 表示行首,美元符号 ($
) 行的结尾,或者,作为最后一个 example点星 (.*
) 表示任何 0、1 或多个字符的序列。 还有很多其他这样的元字符,但就目前而言,这已经绰绰有余了。
因此,要在行首插入一些文本,一个选项是用该文本替换行首:
[email protected]:~$ sed -e 's/<YEAR>/2018/'
-e 's/<COPYRIGHT HOLDER>/Sylvain Leroux/'
-e 's/^/# /'
MIT.LICENSE | head -5
# -----8<----------------------------------------------------------------
# Copyright 2018 Sylvain Leroux
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
4.清除选中的行
sed 中的替换命令非常通用,您可以使用它来表达大多数文本转换。 为了 example,要删除许可证文本顶部和底部的虚线,我可以这样写:
[email protected]:~$ sed -e 's/<YEAR>/2018/'
-e 's/<COPYRIGHT HOLDER>/Sylvain Leroux/'
-e 's/^/# /'
-e 's/^.*----.*$//'
MIT.LICENSE | head -5
# Copyright 2018 Sylvain Leroux
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
后来的替换已用空字符串替换所有文本:
象征 | 描述 |
---|---|
^ | 从行首开始 |
.* | 后跟 0、1 或多个字符的任意序列 |
—- | 后跟 4 个连字符 |
.* | 后跟 0、1 或多个字符的任意序列 |
$ | 紧随其后的是行尾 |
简而言之,如果它连续包含四个破折号,它将用空字符串替换整行。 但是空行本身仍保留在输出中,并将显示为空行。
根据您的确切需求和品味,您可能还需要考虑以下替代解决方案。 我让您详细检查以查明命令中的更改并自行确定对结果的影响:
[email protected]:~$ sed -e 's/<YEAR>/2018/'
-e 's/<COPYRIGHT HOLDER>/Sylvain Leroux/'
-e 's/^.*----.*$//'
-e 's/^/# /'
MIT.LICENSE | head -5
如果您发现用于清除行的正则表达式有点过于复杂,我们也可以利用另一个 sed 功能。 几乎所有命令都可以在命令名称之前使用可选地址。 如果存在,它将命令的范围限制为与该地址匹配的行:
[email protected]:~$ sed -e 's/<YEAR>/2018/'
-e 's/<COPYRIGHT HOLDER>/Sylvain Leroux/'
-e 's/^/# /'
-e '/----/s/^.*$//'
MIT.LICENSE | head -5
现在最新的替换命令将只应用于连续匹配(即“包含”)四个破折号的行。 对于每个匹配的行,它将替换所有内容(.*
) 开始之间 (^
) 和结束 ($
) 由空字符串 (//
)
5.删除选定的行
在上一节中,我们调整了替换命令以清除一些文本行。 但是空行仍然存在。 有时这是可取的。 有时不是。 在后一种情况下,您可能需要调查 delete 命令以从输出中删除整行:
# Below, the redirection '> LICENSE' is used to store
# the result of the sed command into the newly
# created LICENSE file:
[email protected]:~$ sed -e 's/<YEAR>/2018/'
-e 's/<COPYRIGHT HOLDER>/Sylvain Leroux/'
-e 's/^/# /'
-e '/----/d'
MIT.LICENSE > LICENSE
[email protected]:~$ head -5 LICENSE
# Copyright 2018 Sylvain Leroux
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
这 d
是删除命令名称。 就像 s
是替换命令名称。 在这里,我们在命令之前指定了一个地址,因此只会删除匹配的行(没有任何地址, d
命令会删除文件的每一行)
6.转换为大写
到目前为止,我们主要关注许可证文件的顶部。 但确实有一些更改我想在文档中进一步执行一些更改。 让我们先看看我在说什么:
[email protected]:~$ sed -ne '/The above/,$p' LICENSE
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# The software is provided "as is", without warranty of any kind,
# express or implied, including but not limited to the warranties of
# merchantability, fitness for a particular purpose and noninfringement.
# In no event shall the authors or copyright holders be liable for any
# claim, damages or other liability, whether in an action of contract,
# tort or otherwise, arising from, out of or in connection with the
# software or the use or other dealings in the software.
在上面的命令中,使用 -n 选项我禁用了模式空间的自动打印。 这意味着 sed 将不再在输出上打印任何内容,除非我明确要求它这样做。 这正是我使用 print (p) 命令所做的。 请注意,我没有在 p 命令之前使用单个地址,而是使用了一个范围来显示包含文本“The above”的行和文档末尾 ($) 之间的文本。
当您需要提取文件的某些部分时,打印命令会很有用。 然而,就今天而言,我只想显示最后两段来解释我现在需要什么:由于这是许可证文件的传统,我想通过明确说明软件是“按原样”提供的来掩饰自己。 因此,我想强调最后一段(以“软件”开头),将其全部改写为大写。
在替换命令的替换部分, & 被替换为匹配搜索模式的文本。 使用 U GNU 扩展,我们可以更改替换字符串的大小写:
[email protected]:~$ sed -i -e '/The software/,$s/.*/U&/' LICENSE
[email protected]:~$ cat LICENSE
纯文本 s/.*/U&/
意思是“替换任何文本(.*
) 大写 (U
) 自身的版本 (&
)。 我让你自己验证,最后一段现在应该全部大写。 顺便说一句,您可能已经注意到,因为 -i
标志,更改直接应用于许可证文件。
我们将在下一节中更详细地看到这一点。 同时,我让你随意练习和修改这些命令。 一旦你有了一个符合你口味的许可证文件,就该看看如何在项目的每个源文件之前包含它了。
7.插入文本文件
如果您在这里期待一些复杂的命令,您会感到失望:将文件插入另一个文件非常简单:
sed -i -e '1r LICENSE' script1.sh
cat script1.sh
这里有两件事要看:
- 这
r LICENSE
表达式是读取外部文件并将其插入当前正在处理的文件中的命令。 此处以数字为前缀1
这是一个仅匹配输入文件第 1 行的地址。 - 这
-i
选项允许就地更改文件。 这意味着 sed 将在场景后面创建一个临时文件以将其输出存储在那里,并且一旦处理完成,它将用修改后的文件替换原始文件。
‘-i’ 选项的一个有趣的副作用是您可以在命令行上指定多个文件名,并且 sed 将独立地对每个文件应用相同的转换:
sed -i -e '1r LICENSE' *.sh
8.回到未来
作为我们最后 example sed 命令,让我们假设几年过去了,现在是 2024 年 1 月 1 日。所有文件的版权声明必须更新。 有几个用例,具体取决于项目文件的创建时间。 因此,我们的版权声明应遵循以下两种格式之一:
当前版权 | 描述 |
---|---|
版权所有 2023 | 对于去年创建的文件 |
版权所有 2018-2023 | 对于去年之前创建的文件 |
我们可以使用扩展 (-E) 正则表达式一次捕获这两个用例。 我们将在这里真正使用的唯一“扩展”的东西是括号:
sed -i -Ee 's/Copyright (....)(-....)?/Copyright 1-2024/' *.sh
我鼓励您手动修改 *.sh 文件中的版权声明,然后在不同的用例中运行上面的命令,看看它是如何工作的。
如果我在搜索模式中说,它最终可能会帮助您理解: Copyright:: 是一个字面文本,将逐字匹配; (… .):: 定义匹配四个任意字符的捕获组。 希望是一年的四位数; (-… .)?:: 定义一个捕获组,匹配一个破折号后跟四个任意字符。 末尾的问号表示该组是可选的。 它可能出现在输入行中,也可能不出现。
在替换字符串中: Copyright:: 是一个文字文本,将被逐字复制; 1:: 是第一个捕获组的内容 -2024:: 是将逐字复制的文字文本。
如果您花时间自己检查命令,它应该确认我是否将这些规则应用于上表中描述的用例,我会得到类似的东西:
匹配文本 | 1 | 2 | 替换字符串 |
---|---|---|---|
版权所有 2023 | 2023 | 版权所有 2023-2024 | |
版权所有 2018-2023 | 2018 | -2023 | 版权所有 2018-2024 |
结束我们的 SED 指南
我们在这里只触及了表面。 这 sed
工具比这更强大。 然而,即使我们只看到了四个命令(s
, p
, d
, 和 i
) 和一些基本的正则表达式结构 (^
, $
, .
, ?
和 .*
),您已经有足够的知识来解决许多日常问题。
因为我喜欢用一个小挑战来结束一个教程,所以我建议你:如果你已经下载了 支持材料,你会在项目目录中找到一个名为 hello.c
. 这是一个基本 C 程序的源文件:
[email protected]:~$ ls
hello.c MIT.LICENSE script1.sh script2.sh
[email protected]:~$ gcc hello.c -o hello
[email protected]:~$ ./hello sylvain
Hello sylvain
[email protected]:~$ cat hello.c
源文件中已经有一些注释。 通过将它们用作 C 编程语言中注释语法的示例,您能否将 MIT 许可证插入到 hello.c
使用 sed 命令的源文件? 您可以使用一个或多个 sed 命令,可以将 sed 命令的输出通过管道传输到另一个命令,如果需要,可以使用临时文件,但不允许使用除 sed 之外的任何其他命令。 当然,插入许可证后,C 源文件应该仍然可以编译!
我现在让你想想那个小问题,我希望你喜欢那篇文章及其 配套视频. 如果您想了解更多关于 sed 的信息,请在评论部分告诉我们!