使用N时会被sed输出混淆。有人可以解释这些结果吗?


8

我正在学习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”结尾的行。没问题。但我也想抓住所有其他“网络管理员”单行实例。

从我的研究中,我了解到我将需要两个替换命令。一个用于换行符分隔的字符串,另一个用于其他字符串。另外,由于最后一行包含替换匹配项,而下一行包含多行,因此发生了一些冲突。所以我做这个...

$ 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脚本却无效?我真的很想了解这一点。

在此先感谢您的帮助。


很高兴学习Sed!我使用Sed在此站点上解决了一个非常类似的问题。可能很有趣。
通配符

Answers:


6

在学习的过程中sed,我将花时间添加到@ John1024的答案中:

1)请注意,您正在使用\n替换字符串。这在GNU中有效sed,但不是POSIX的一部分,因此它将n在许多其他seds中插入反斜杠和一个(\n在模式中使用可移植,顺便说一句)。

相反,我建议您这样做s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g[[:space:]]将匹配换行符或空格,因此您不需要两个s命令,而是将它们组合在一起。通过将其包围起来,\(...\)您可以在替换中引用它:\1将会被替换为第一对中匹配的内容\(\)

2)要在两行上正确匹配模式,您应该知道该N;P;D模式:

 sed '$!N;s/Network\([[:space:]]\)Administrator/System\1User/g;P;D'

N始终追加下一行(除了最后一行,这就是为什么它与“解决” $!(=如果不是最后一行,你应该总是考虑到preceed N$!避免意外结束脚本)然后更换后,P只打印模式空间中的第一行,然后D删除该行,并使用模式空间的其余部分(不读取下一行)开始下一个循环。

记住这种模式,您将经常需要它。

3)多行编辑的另一种有用模式,尤其是涉及多于两行时:如我对约翰的建议,保持空间收集:

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'

我重复它来解释它:H将每一行追加到保留空间。由于这会导致在第一行之前出现额外的换行符,因此需要移动第一行而不是将其附加到1h。下面的$!d意思是“对于最后一行以外的所有行,删除模式空间并重新开始”。因此,脚本的其余部分仅在最后一行执行。此时,整个文件将被收集在保留空间中(因此,不要将其用于非常大的文件!)并将其g移至模式空间,因此您可以像-z选择一样一次性进行所有替换。 GNU sed

我建议记住这是另一个有用的模式。


哇!很好的解释!加上John的回答,确实使我对这个问题有了更好的了解,并且总的来说很满意。看来我还有很多东西要学。希望我能将您的两种解决方案都作为答案。非常感谢您的付出。他们非常感谢。
dlowrie290

7

首先,请注意您的解决方案并没有真正起作用。考虑以下测试文件:

$ 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

问题是代码不能代替last 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。

[ Philippos的帽子提示。]

多行版本

如果有助于澄清,则以下是同一命令,分为多行:

$ sed ':a
    /Network$/{
       $!{
           N
           ba
       }
    }
    s/Network\nAdministrator/System\nUser/g
    s/Network Administrator/System User/g
    ' filename

怎么运行的

  1. :a

    这将创建一个标签a

  2. /Network$/{ $!{N;ba} }

    如果该行以结束Network,那么,如果不是最后一行($!),则读取并附加下一行(N),然后分支回到标签aba)。

  3. s/Network\nAdministrator/System\nUser/g

    用中间的换行符进行替换。

  4. 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

在这种情况下,让-zsed读取最多第一个NUL字符。由于文本文件永远不会包含空字符,因此具有一次读取整个文件的效果。然后,我们可以进行替换而不必担心丢失一行。

如果文件很大(通常表示千兆字节),则此方法不好。如果太大,则一次读取全部内容可能会使系统RAM紧张。

适用于GNU和BSD sed的解决方案

Phillipos所建议,以下是可移植的解决方案:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1Us‌​er/g'

1
很好的信息,约翰!感谢您对此发表一些看法,您的替代解决方案非常好。话虽如此,我仍然不明白为什么我的解决方案不是解决方案。它似乎可以工作,但是对于您的test.txt文件却无效。为什么我的解决方案似乎有效,但实际上不起作用吗?非常感谢帮忙。
dlowrie290

1
@ dlowrie290您的解决方案成对读取。如果Network Administrator在该对的第一行和第二行之间进行拆分,则您的解决方案将成功进行替换。然后打印这两行并在下一对中读取。但是,如果第一对的第二行以结束,Network第二对的第一行以开头Administrator,则代码会错过它。我的代码通过逐行读取直到找到不以结尾的代码来避免这种情况Network
John1024年

2
请注意,您的第一个多行解决方案还取决于对的GNU扩展sed\n替换中的未在标准中定义。sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1User/g'是一种便携式的方法。
Philippos

@Philippos优秀积分。答案已更新为包含便携式解决方案。
John1024

1
约翰,谢谢您的澄清!同样,非常感谢您提供的宝贵资源和您的时间/精力!
dlowrie290
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.