替换包含换行符的字符串


10

使用bash外壳程序,在具有以下行的文件中

first "line"
<second>line and so on

我想更换的一次或多次出现"line"\n<second>other characters,并获得每一次:

first other characters line and so on

因此,我必须用特殊字符(例如"和)<和换行符来替换字符串。

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

有没有一种方法(比this更简单)使用sed或获得此结果grep


您正在使用Mac吗?\n您发表的野蛮言论是我问的原因。人们很少问他们s//\n/是否可以像使用GNU 那样做sed,尽管大多数其他人sed都会拒绝右侧的逃逸。尽管如此,\n转义仍然sed可以在任何POSIX的左侧使用,并且您可以像对其一样轻巧地进行翻译,y/c/\n/尽管它具有与POSIX 相同的效果s/c/\n/g,因此并不总是那么有用。
mikeserv

Answers:


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///ubstitution命令为基础:

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

他们也都尽力处理最后一行,因为seds在边缘情况下的输出往往会有所不同。这就是其含义,$!它是匹配!不是$最后一行的每一行的地址。

它们也都使用Next命令将下一条输入\n字符后面的输入行追加到图案空间。任何已经唱歌sed了一段时间的人都会学会依靠\n腰线字符-因为唯一获得该字符的方法是将其明确放置在此处。

这三者都采​​取了一些尝试,以便在采取行动之前尽可能少地读取输入内容- sed尽快采取行动,不需要在读取整个输入文件之前就这样做。

尽管它们全部完成N,但它们三个递归方法都不同。

第一命令

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

  • N-如前所述,在N插入的\n行分隔符之后,将ext输入行追加到模式空间。
  • Pp; 它会P漂洗图案空间-但只保留到第一个出现的\newline字符。因此,给出以下输入/命令:

    • printf %s\\n one two | sed '$!N;P;d'
  • sed P只漂一只。但是,随着...

  • Dd; 它D删除模式空间并开始另一个行循环。不同于 dD最多仅删除\n模式空间中第一个出现的ewline。如果在\n后跟字符之后的模式空间中有更多内容,请sed从下一个行循环开始,再继续剩余行。d例如,如果将先前示例中的替换为a Dsed则将同时P撕裂

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

可以进行测试以应用P和/或D选择性地应用,但是还有其他更适合该策略的命令。因为实现了递归来处理仅匹配替换规则一部分的连续行,所以匹配ubstitution 两端的连续行序列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标签(如也证明Joeseph R.的答案在这里和递归回到它在一定条件下。

  • -e :n -e-可移植sed脚本将:使用\newline或新的内联-execution语句来分隔标签定义。
    • :n-定义名为的标签n。您可以随时使用bn或将其返回tn
  • tn- 如果自定义标签以来或自上次成功调用ests以来发生任何故障,则t est命令返回指定的标签(如果未提供,则退出当前行周期的脚本)s///t

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

这倾向于更好地处理连续序列。如果最后一个失败,则输出:

first other characters other characters other characters line and so on

第三指挥

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

  • /"$/bn-这是sed测试。由于branch命令是此地址的函数,sed因此仅在附加了ewline并且模式空间仍以双引号结束之后才可以b牧场返回。:n\n"

有作为之间几乎没有做过Nb地-这样sed可以准确地非常迅速收集尽可能多的输入作为必须保证以下线路不能满足您的规则。s///此处的ubstitution不同之处在于,它使用了global标志-因此它将立即进行所有必要的替换。给定相同的输入,此命令的输出与最后一个相同。


很抱歉,这个琐碎的问题是什么意思,DATA您怎么收到文本输入?
BowPark 2014年

@BowPark-在此示例<<\DATA\ntext input\nDATA\n中,该文件已生成,但这仅sed是shell在here文档中传递的文本。它会像sed 'script' filename或一样工作process that writes to stdout | sed 'script'。有帮助吗?
mikeserv 2014年

是的,谢谢!为什么没有D所有修改的行都是双行?(您在必要时使用了它;也许我不太了解sed
BowPark 2014年

1
@BowPark-省略时,您会翻倍,D因为D否则D将从输出中删除您现在看到的翻倍。我刚刚进行了编辑-我可能很快也会对此进行扩展。
mikeserv 2014年

1
@BowPark-好的,我已经更新了它并提供了选项。现在可能更容易阅读/理解。我也明确指出了这D件事。
mikeserv

7

好吧,我可以想到几种简单的方法,但是都不涉及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
    

    或者,要将多个连续出现的"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\n为对整个文件进行处理,然后将输出记录分隔符设置为空(否则将打印额外的换行符),然后使用该sub()函数进行替换。


2
@mikeserv?哪一个?第二个应该是,OP表示他们希望“替换一个或多个事件”,因此吃掉该段很可能是他们所期望的。
terdon

很好的一点。我想我会更专注于每次获取,但是我想不清楚是应该每次替换一次还是每次替换序列一次……@BowPark?
mikeserv 2014年

每次需要更换一次。
BowPark

@BowPark好,那么第一个perl方法或awk都应该起作用。他们没有给您想要的输出吗?
terdon

可以,谢谢,但是它的第三行awk应该是print;}' file。我需要避免使用Perl并最好使用sed,无论如何,您都建议使用其他好的选择。
BowPark

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
first other characters  line followed by other characters  and last

是。它有效,但是如果我多次出现该怎么办?
BowPark 2014年


1
抱歉再次nitpick,但是它${cmds}是GNU特定的-其他大多数seds都需要\n加粗线或-ep和之间断开}。您可以完全避开括号,而且可以\n方便地携带,甚至可以避免在第一行插入多余的斜线字符,例如:sed 'H;1h;$!d;x;s/"line"\n<second>/other characters /g'
mikeserv 2014年

我对其进行了测试,但它似乎不可移植。它在输出的开头打印一条额外的换行符,但是结果在GNU上是正确的。
BowPark

删除主要的换行符:sed -n '1{h;n};H; ${x; s/"line"\n<second>/other characters /g; p}'-但是,这变得难以维护。
格伦·杰克曼

3

这是glenn答案的一种变体,如果您连续多次出现(sed仅适用于GNU ),该变体将起作用:

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

:x只是分支的标签。基本上,这样做是在替换后检查该行,如果仍然匹配"line",则分支回到:x标签(这是正确的bx),然后向缓冲区添加另一行并开始处理它。


@mikeserv请具体说明您的意思。它为我工作。
约瑟夫R.14年

@mikeserv对不起,我真的不知道您在说什么。我将上面的代码行复制回了我的终端,它可以正常工作。
约瑟夫R.14年

1
缩回-这显然在GNU中确实起作用,sed它使非POSIX标签处理足够远,可以接受一个空格作为标签声明的分隔符。不过,您应该注意,其他任何地方sed都将失败-并且将针对失败N。GNU 在最后一行sed上的a退出之前违反了POSIX准则来打印模式空间N,但是POSIX清楚地表明,如果N在最后一行上读取命令,则不应打印任何内容
mikeserv 2014年

如果您编辑该帖子以指定GNU,我将撤回我的表决并删除这些评论。另外,可能值得学习GNU的v命令,该命令会相互中断,sed但在GNU 4和更高版本中是空手。
mikeserv

1
在这种情况下,我会提供一个更多-这是可以做到便携,如:sed -e :x -e '/"line"/{$!N' -e '};s/"line"\n<second>/other characters/;/"line"/bx'
mikeserv
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.