使用 N 时对 sed 输出感到困惑。有人可以解释这些结果吗?

使用 N 时对 sed 输出感到困惑。有人可以解释这些结果吗?

我正在学习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

怎么运行的

  1. :a

    这将创建一个标签a

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

    如果该行以 结尾Network,那么,如果这是不是最后一行 ( $!) 读取并附加下一行 ( N) 并分支回标签a( ba)。

  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

在本例中,-z告诉 sed 读入第一个 NUL 字符。由于文本文件永远不会有空字符,因此这具有一次读取整个文件的效果。然后我们就可以进行替换而不必担心丢失一行。

如果文件很大(通常意味着千兆字节),则此方法不太好。如果它那么大,那么一次将其全部读取可能会导致系统 RAM 紧张。

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

正如建议的菲利波斯,以下是一个便携式解决方案:

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

答案2

当你学习时sed,我会花时间添加到@John1024的答案中:

\n1) 请注意您在替换字符串中使用。这在 GNU 中有效sed,但不是 POSIX 的一部分,因此它将n在许多其他seds 中插入反斜杠和 an (\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附加下一行(除了最后一行,这就是为什么它被“寻址”为$!(=如果不是最后一行;您应该始终考虑继续N$!避免意外结束脚本)。然后在替换后P仅打印模式空间中的第一行,然后D删除该行并使用模式空间的剩余部分开始下一个循环(不读取下一行)这可能是您最初的意图。

记住这个模式,你会经常需要它。

3) 多行编辑的另一个有用模式,特别是当涉及两行以上时:保持空间收集,正如我向约翰建议的那样:

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

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

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

相关内容