替换包含换行符的字符串

Bow*_*ark 10 grep sed text-processing newlines

使用bash外壳,在具有如下行的文件中

first "line"
<second>line and so on
Run Code Online (Sandbox Code Playgroud)

我想每次都替换一次或多次出现的"line"\n<second>withother characters并获得:

first other characters line and so on
Run Code Online (Sandbox Code Playgroud)

所以我必须用特殊字符(例如"和 )<和换行符替换字符串。

在其他答案之间搜索后,我发现sed可以接受命令右侧的换行符(因此,other characters字符串),但不能接受左侧的换行符。

有没有办法(比更简单)用sed或获得这个结果grep

ter*_*don 8

好吧,我可以想到几种简单的方法,但都不涉及grep(无论如何都不会进行替换)或sed.

  1. 珀尔

    要更换每个发生"line"\n<second>other characters,使用:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    
    Run Code Online (Sandbox Code Playgroud)

    或者,要将多个连续出现的"line"\n<second>视为一个,并将它们全部替换为单个other characters,请使用:

    perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    
    Run Code Online (Sandbox Code Playgroud)

    例子:

    $ cat file
    first "line"
    <second>"line"
    <second>"line"
    <second>line and so on
    $ perl -00pe 's/(?:"line"\n<second>)+/other characters /g' file
    first other characters line and so on
    
    Run Code Online (Sandbox Code Playgroud)

    -00导致Perl来读取,这意味着“线”被定义为“段落模式”的文件\n\n,而不是\n本质上,每个段落被视为一条线。因此,替换匹配跨换行符。

  2. awk

    $  awk -v RS="\n\n" -v ORS="" '{
          sub(/"line"\n<second>/,"other characters ", $0)
          print;
        }' file 
    first other characters line and so on
    
    Run Code Online (Sandbox Code Playgroud)

    同样的基本思想,我们将记录分隔符(RS)设置\n\n为 slurp 整个文件,然后将输出记录分隔符设置为空(否则会打印额外的换行符),然后使用该sub()函数进行替换。

  • @mikeserv?哪一个?第二个应该是,OP 说他们想要“替换一个或多个出现的”,所以吃这一段很可能是他们所期望的。 (2认同)

gle*_*man 6

读取整个文件并进行全局替换:

sed -n 'H; ${x; s/"line"\n<second>/other characters /g; p}' <<END
first "line"
<second> line followed by "line"
<second> and last
END
Run Code Online (Sandbox Code Playgroud)
first other characters  line followed by other characters  and last
Run Code Online (Sandbox Code Playgroud)


mik*_*erv 3

三个不同的sed命令:

sed '$!N;s/"[^"]*"\n<[^>]*>/other characters /;P;D'

sed -e :n -e '$!N;s/"[^"]*"\n<[^>]*>/other characters /;tn'

sed -e :n -e '$!N;/"$/{$!bn' -e '};s/"[^"]*"\n<[^>]*>/other characters /g'
Run Code Online (Sandbox Code Playgroud)

它们三个都建立在基本的s///替换命令之上:

s/"[^"]*"\n<[^>]*>/other characters /
Run Code Online (Sandbox Code Playgroud)

他们还尝试小心处理最后一行,因为sed在边缘情况下其输出往往会有所不同。这就是它的含义是匹配不是最后$!一行的每一行的地址。!$

它们还都使用Next 命令将下一个输入行附加到模式空间中的\newline 字符后面。任何已经sed使用了一段时间的人都会学会依赖\newline 字符 - 因为获得该字符的唯一方法就是明确地将其放在那里。

所有这三个都在采取行动之前尝试读入尽可能少的输入 -sed尽可能快地采取行动,并且在此之前不需要读入整个输入文件。

尽管它们都做了N,但它们三者的递归方法都不同。

第一个命令

第一个命令使用一个非常简单的N;P;D循环。这三个命令内置于任何 POSIX 兼容的命令中sed,并且它们可以很好地相互补充。

  • N- 如前所述,将Next 输入行附加到插入的 ewline 分隔符之后的模式空间\n
  • P- 喜欢p; 它P打印模式空间 - 但仅限于第一个出现的\newline 字符。因此,给出以下输入/命令:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed P仅打印一个. 然而,随着...

  • D- 喜欢d; 它D删除模式空间并开始另一个行循环。与 不同的是 dD仅删除\n模式空间中第一个出现的 ewline。\n如果ewline 字符后面的模式空间中有更多内容,sed则使用剩余的内容开始下一个行循环。例如,如果d将上一个示例中的 替换为 a ,则将同时打印oneTwoDsedP

此命令仅对与ubstitution 语句匹配的行进行递归s///。因为s///ubstitution 删除了\n添加的 ewline ,所以当eletes 模式空间N时永远不会剩下任何东西。sed D

可以进行测试来选择性地应用P和/或D,但还有其他更适合该策略的命令。由于实现递归是为了处理仅与替换规则的一部分匹配的连续行,因此与替换两端匹配的连续行序列s///效果不佳:

鉴于此输入:

first "line"
<second>"line"
<second>"line"
<second>line and so on
Run Code Online (Sandbox Code Playgroud)

...它打印...

first other characters "line"
<second>other characters line and so on
Run Code Online (Sandbox Code Playgroud)

然而,它确实处理

first "line"
second "line"
<second>line
Run Code Online (Sandbox Code Playgroud)

...正好。

第二条命令

该命令与第三个命令非常相似。两者都采用:branch/ test 标签(正如 Joeseph R. 的回答中所演示的那样并在给定某些条件的情况下递归回它。

  • -e :n -e- 可移植脚本将使用ewline 或新的内联执行语句来sed分隔标签定义。 :\n-e
    • :n- 定义一个名为 的标签n。可以随时使用 或 返回此bn内容tn
  • tn- test 命令返回到指定的标签(或者,如果没有提供,则退出当前行周期的脚本),如果s///自定义标签或自上次调用tests 以来有任何替换成功。

在此命令中,对匹配行进行递归。如果成功用其他字符sed替换模式,则返回标签并重试。如果未执行替换,则会自动打印模式空间并开始下一个行周期。sed:ns///sed

这往往可以更好地处理连续序列。如果最后一个失败,则打印:

first other characters other characters other characters line and so on
Run Code Online (Sandbox Code Playgroud)

第三条命令

如前所述,这里的逻辑与上一个非常相似,但测试更加明确。

  • /"$/bn- 这是sed测试。因为branch 命令是该地址的函数,所以只有在附加 ewline 且模式空间仍以双引号结束后sed才会branch 返回。:n\n"

N在和 之间尽可能少地完成b- 通过这种方式sed可以非常快速地收集所需的精确输入,以确保下一行无法匹配您的规则。替换s///的不同之处在于它使用g全局标志 - 因此它将立即进行所有必要的替换。给定相同的输入,此命令的输出与上一个命令相同。