我正在学习sed。一切似乎都很顺利,直到我遇到 N(接下来是多行)。我创建此文件 (guide.txt) 是为了练习/理解/上下文目的。这是该文件的内容...
This guide is meant to walk you through a day as a Network
Administrator. By the end, hopefully you will be better
equipped to perform your duties as a Network Administrator
and maybe even enjoy being a Network Administrator that much more.
Network Administrator
Network Administrator
I'm a Network Administrator
所以我的目标是用“系统用户”替换“网络管理员”的所有实例。因为“网络管理员”的第一个实例由换行符 (\n) 分隔,所以我需要多行下一个运算符 (N) 附加以“管理员”开头的行以及以“网络\n”结尾的上一行。没问题。但我还想捕获所有其他“网络管理员”单线实例。
从我的研究中,我了解到我需要两个替换命令;一种用于换行符分隔的字符串,另一种用于其他字符串。另外,由于最后一行包含替换匹配和接下来的多行,因此发生了一些 jive。所以我制作这个...
$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt
这将返回这些结果...
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a Network Administrator that much more.
System User
Network Administrator
I'm a System User
我认为单行替换会捕获“网络管理员”的所有“正常”实例并将其交换为“系统用户”,而多行语句将在换行符分隔的实例上发挥其魔力,但是当您可以看到它返回了,我考虑的是,意想不到的结果。
经过一番摆弄后,我找到了这个......
$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt
瞧,我得到了所需的输出......
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User
为什么这个可以工作,而原始的 sed 脚本却不能?我真的很想明白这一点。
预先感谢您的任何帮助。
答案1
首先,请注意您的解决方案实际上并不起作用。考虑这个测试文件:
$ cat test1
Network
Administrator Network
Administrator
然后运行命令:
$ sed '
s/Network Administrator/System User/
N
s/Network\nAdministrator/System\nUser/
s/Network Administrator/System User/
' test1
System
User Network
Administrator
问题是代码没有替换最后一个Network\nAdministrator
。
这个解决方案确实有效:
$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User
我们还可以将此应用到您的guide.txt
:
$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' guide.txt
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User
关键是继续逐行阅读,直到找到符合要求的内容不是结束于Network
.完成后,就可以进行替换。
兼容性注意事项:以上所有内容均\n
在替换文本中使用。这需要 GNU sed。它不适用于 BSD/OSX sed。
[向菲利浦斯.]
多行版本
如果它有助于澄清,这里是将同一命令拆分为多行:
$ sed ':a
/Network$/{
$!{
N
ba
}
}
s/Network\nAdministrator/System\nUser/g
s/Network Administrator/System User/g
' filename
怎么运行的
:a
这将创建一个标签
a
。/Network$/{ $!{N;ba} }
如果该行以 结尾
Network
,那么,如果这是不是最后一行 ($!
) 读取并附加下一行 (N
) 并分支回标签a
(ba
)。s/Network\nAdministrator/System\nUser/g
用中间换行符进行替换。
s/Network Administrator/System User/g
用中间空白进行替换。
更简单的解决方案(仅限 GNU)
使用 GNU sed (不是BSD/OSX),我们只需要一个替代命令:
$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' test1
System
User System
User
并在guide.txt
文件上:
$ sed -zE 's/Network([[:space:]]+)Administrator/System\1User/g' guide.txt
This guide is meant to walk you through a day as a System
User. By the end, hopefully you will be better
equipped to perform your duties as a System User
and maybe even enjoy being a System User that much more.
System User
System User
I'm a System User
在本例中,-z
告诉 sed 读入第一个 NUL 字符。由于文本文件永远不会有空字符,因此这具有一次读取整个文件的效果。然后我们就可以进行替换而不必担心丢失一行。
如果文件很大(通常意味着千兆字节),则此方法不太好。如果它那么大,那么一次将其全部读取可能会导致系统 RAM 紧张。
适用于 GNU 和 BSD sed 的解决方案
正如建议的菲利波斯,以下是一个便携式解决方案:
sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'
答案2
当你学习时sed
,我会花时间添加到@John1024的答案中:
\n
1) 请注意您在替换字符串中使用。这在 GNU 中有效sed
,但不是 POSIX 的一部分,因此它将n
在许多其他sed
s 中插入反斜杠和 an (\n
顺便说一句,在模式中使用是可移植的)。
相反,我建议这样做s/Network\([[:space:]]\)Administrator/System\1User/g
:[[:space:]]
将匹配换行符或空格,因此您不需要两个s
命令,而是将它们合并为一个命令。通过用 包围它,\(...\)
您可以在替换中引用它:\1
将被第一对中匹配的内容替换\(\)
。
2) 要正确匹配两行上的模式,您应该知道该N;P;D
模式:
sed '$!N;s/Network\([[:space:]]\)Administrator/System\1User/g;P;D'
总是N
附加下一行(除了最后一行,这就是为什么它被“寻址”为$!
(=如果不是最后一行;您应该始终考虑继续N
以$!
避免意外结束脚本)。然后在替换后P
仅打印模式空间中的第一行,然后D
删除该行并使用模式空间的剩余部分开始下一个循环(不读取下一行)这可能是您最初的意图。
记住这个模式,你会经常需要它。
3) 多行编辑的另一个有用模式,特别是当涉及两行以上时:保持空间收集,正如我向约翰建议的那样:
sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1User/g'
我重复一遍来解释它:H
将每一行附加到保留空间。由于这会导致第一行之前出现额外的换行符,因此需要移动第一行而不是附加1h
.以下$!d
意思是“对于除最后一行之外的所有行,删除模式空间并重新开始”。因此,脚本的其余部分仅针对最后一行执行。此时,整个文件被收集在保留空间中(因此不要将其用于非常大的文件!)并将其移动到模式空间,因此您可以像使用以下选项g
一样立即执行所有替换-z
GNU sed
。
这是我建议记住的另一个有用的模式。