替换包含换行符的字符串

替换包含换行符的字符串

使用bashshell,在包含如下行的文件中

first "line"
<second>line and so on

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

first other characters line and so on

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

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

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

答案1

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

  1. Perl

    取代每个出现"line"\n<second>with other characters,使用:

    $ perl -00pe 's/"line"\n<second>/other characters /g' file
    first other characters line and so on
    

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

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

    例子:

    $ 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
    

    -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
    

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

答案2

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

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

答案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'

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

s/"[^"]*"\n<[^>]*>/other characters /

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

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

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

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

第一个命令

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

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

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

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

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

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

鉴于此输入:

first "line"
<second>"line"
<second>"line"
<second>line and so on

...它打印...

first other characters "line"
<second>other characters line and so on

然而,它确实可以处理

first "line"
second "line"
<second>line

...正好。

第二条命令

该命令与第三个命令非常相似。两者都采用:b牧场/ test 标签(Jooseph R. 的回答也证明了这一点这里并在给定某些条件的情况下递归回它。

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

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

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

first other characters other characters other characters line and so on

第三条命令

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

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

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

答案4

这是一个变体格伦的回答如果您有多个连续出现的情况,这将起作用(sed仅适用于 GNU):

sed ':x /"line"/N;s/"line"\n<second>/other characters/;/"line"/bx' your_file

:x只是分支的标签。基本上,它的作用是检查替换后的行,如果它仍然匹配"line",它会分支回:x标签(这就是所做的bx)并将另一行添加到缓冲区并开始处理它。

相关内容