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

dlo*_*290 8 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
Run Code Online (Sandbox Code Playgroud)

所以我的目标是用“系统用户”替换“网络管理员”的所有实例。因为“网络管理员”的第一个实例由换行符 (\n) 分隔,我需要多行下一个运算符 (N) 来附加以“管理员”开头的行,前一行以“网络\n”结尾. 没问题。但我也想捕获所有其他“网络管理员”单行实例。

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

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> ' guide.txt
Run Code Online (Sandbox Code Playgroud)

这将返回这些结果...

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
Run Code Online (Sandbox Code Playgroud)

我认为单行替换会捕获“网络管理员”的所有“正常”实例并将其替换为“系统用户”,而多行语句会在换行符分隔的实例上发挥其魔力,但是当你可以看到它返回,我认为,意外的结果。

经过一番摆弄后,我降落在这个......

$ sed '
> s/Network Administrator/System User/
> N
> s/Network\nAdministrator/System\nUser/
> s/Network Administrator/System User/
> ' guide.txt
Run Code Online (Sandbox Code Playgroud)

瞧,我得到了所需的输出......

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
Run Code Online (Sandbox Code Playgroud)

为什么这行得通而原始 sed 脚本不行?我真的很想了解这一点。

在此先感谢您的帮助。

Joh*_*024 7

首先,请注意您的解决方案并不真正有效。考虑这个测试文件:

$ cat test1
Network
Administrator Network
Administrator
Run Code Online (Sandbox Code Playgroud)

然后运行命令:

$ sed '
 s/Network Administrator/System User/
 N
 s/Network\nAdministrator/System\nUser/
 s/Network Administrator/System User/
 ' test1
System
User Network
Administrator
Run Code Online (Sandbox Code Playgroud)

问题是代码不能替代最后一个Network\nAdministrator.

此解决方案确实有效:

$ sed ':a; /Network$/{$!{N;ba}}; s/Network\nAdministrator/System\nUser/g; s/Network Administrator/System User/g' test1
System
User System
User
Run Code Online (Sandbox Code Playgroud)

我们也可以将其应用于您的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
Run Code Online (Sandbox Code Playgroud)

关键是继续按行阅读,直到找到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
Run Code Online (Sandbox Code Playgroud)

这个怎么运作

  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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

正如Phillipos所建议的,以下是一个便携式解决方案:

sed 'H;1h;$!d;x;s/Network\([[:space:]]\)Administrator/System\1Us??er/g'
Run Code Online (Sandbox Code Playgroud)

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

Phi*_*pos 6

在您学习的过程中sed,我会花时间补充@John1024 的回答:

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

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

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

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

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

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

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

sed 'H;1h;$!d;g;s/Network\([[:space:]]\)Administrator/System\1Us??er/g'
Run Code Online (Sandbox Code Playgroud)

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

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