如何使用sed替换多行字符串?


243

我注意到,如果我添加\n了一个模式以替换使用sed,则该模式不匹配。例:

$ cat > alpha.txt
This is
a test
Please do not
be alarmed

$ sed -i'.original' 's/a test\nPlease do not/not a test\nBe/' alpha.txt

$ diff alpha.txt{,.original}

$ # No differences printed out

我该如何工作?


此处的智能解决方法:unix.stackexchange.com/a/445666/61742。当然这不是表演!根据您的需要执行替换的其他不错的选择可能是awk,perl和python。还有许多其他功能,但是我相信awk在各种Linux发行版中是最通用的(例如)。谢谢!
爱德华多·卢西奥

Answers:


235

在最简单的调用sed的,它有一个模式空间,即文本行。\n输入的1行分隔文本。模式空间中的单行没有\n...这就是您的正则表达式找不到任何内容的原因。

您可以将多行读入模式空间,并且可以出乎意料地很好地进行操作,但是要比平常付出更多的努力。.Sed具有一组允许这种类型的命令的命令。。这是我找到的最好的,并且让我滚动。

但是,一旦开始使用sed的微命令,请不要忘记“单线”想法。将其像结构化程序一样进行布局是很有用的,直到您获得它的感觉为止。它非常简单,而且同样不寻常。您可以将其视为文本编辑的“汇编语言”。

简介:将sed用于简单的事情,甚至可能更多,但是总的来说,当它超出了单行的工作范围时,大多数人会喜欢其他东西……
我会让别人建议其他东西。真的不确定最好的选择是什么(我会使用sed,但这是因为我不太了解perl。)


sed '/^a test$/{
       $!{ N        # append the next line when not on the last line
         s/^a test\nPlease do not$/not a test\nBe/
                    # now test for a successful substitution, otherwise
                    #+  unpaired "a test" lines would be mis-handled
         t sub-yes  # branch_on_substitute (goto label :sub-yes)
         :sub-not   # a label (not essential; here to self document)
                    # if no substituion, print only the first line
         P          # pattern_first_line_print
         D          # pattern_ltrunc(line+nl)_top/cycle
         :sub-yes   # a label (the goto target of the 't' branch)
                    # fall through to final auto-pattern_print (2 lines)
       }    
     }' alpha.txt  

这里是相同的脚本,浓缩为明显更难阅读和使用的脚本,但有些人怀疑将其称为“ 单行”

sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;ty;P;D;:y}}' alpha.txt

这是我的命令“备忘单”

:  # label
=  # line_number
a  # append_text_to_stdout_after_flush
b  # branch_unconditional             
c  # range_change                     
d  # pattern_delete_top/cycle          
D  # pattern_ltrunc(line+nl)_top/cycle 
g  # pattern=hold                      
G  # pattern+=nl+hold                  
h  # hold=pattern                      
H  # hold+=nl+pattern                  
i  # insert_text_to_stdout_now         
l  # pattern_list                       
n  # pattern_flush=nextline_continue   
N  # pattern+=nl+nextline              
p  # pattern_print                     
P  # pattern_first_line_print          
q  # flush_quit                        
r  # append_file_to_stdout_after_flush 
s  # substitute                                          
t  # branch_on_substitute              
w  # append_pattern_to_file_now         
x  # swap_pattern_and_hold             
y  # transform_chars                   

167
现在射杀我。有史以来最糟糕的语法!
吉利2014年

53
这是一个很棒的解释,但我倾向于同意@Gili。
gatoatigrado 2014年

11
您的备忘单已包含所有内容。
konsolebox

3
您无需标签即可在t此处使用该命令-当未提供标签时,默认情况下将跳转到脚本末尾。所以sed '/^a test$/{$!{N;s/^a test\nPlease do not$/not a test\nBe/;t;P;D}}' alpha.txt不完全一样,在任何情况下你的命令。当然,对于此特定文件,sed '/test/{N;s/.*/not a test\nBe/}' alpha.txt也执行相同的操作,但是我的第一个示例在逻辑上等效于所有可能的文件。另请注意,\n在替换字符串中不会产生换行符;您需要反斜杠`\`,然后再加上实际的换行符来执行此操作。
2015年

9
请注意,语法是GNU特定的(#命令与\nRHS中的上一个命令没有分开s)。使用GNU,sed您还-z可以使用NUL分隔的记录(然后将整个输入内容(如果为文本)(根据定义,其中不包含NUL)添加进来)。
斯特凡Chazelas

181

使用perl代替sed

$ perl -0777 -i.original -pe 's/a test\nPlease do not/not a test\nBe/igs' alpha.txt
$ diff alpha.txt{,.original}
2,3c2,3
< not a test
< Be
---
> a test
> Please do not

-pi -e是您的标准“就地替换”命令行序列,并且-0777使perl吞噬整个文件。有关更多信息,请参见perldoc perlrun


3
谢谢!对于多行工作,perl胜出了!我最终使用`$ perl -pi -e's / bar / baz /'fileA`来就地更改文件。
Nicholas Tolley Cottrell

3
原始张贴者要求sed使用awk或perl 进行答复并出现是很常见的。我认为这不是主题,因此,抱歉,我开了一分。
Rho Phi

68
+1并不同意罗伯托。通常,问题的措辞是专门针对更好的方法的无知。当没有实质性的上下文差异时(如此处),最佳解决方案应至少具有与特定问题的解决方案相同的轮廓。
geotheory 2015年

56
我认为sed以上答案证明了Perl的答案是话题。
reinierpost 2015年

7
容易一些:使用“ -p0e”,不需要“ -0777”。unix.stackexchange.com/a/181215/197502
Weidenrinde

96

我认为,最好\n用其他符号替换符号,然后照常工作:

例如未工作的源代码:

cat alpha.txt | sed -e 's/a test\nPlease do not/not a test\nBe/'

可以更改为:

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test\rPlease do not/not a test\rBe/'  | tr '\r' '\n'

如果有人不知道,\n是UNIX行结束,\r\n-窗口\r-经典的Mac OS。普通的UNIX文本不使用\r符号,因此在这种情况下可以安全地使用它。

您还可以使用一些奇异符号临时替换\ n。例如-\ f(换页符号)。您可以在此处找到更多符号。

cat alpha.txt | tr '\n' '\f' | sed -e 's/a test\fPlease do not/not a test\fBe/'  | tr '\f' '\n'

11
为这个聪明的hack +1!除非您绝对确定要编辑的文件的内容,否则有关使用外来符号临时替换换行符的建议尤其有用。
L0j1k 2015年

这并不工作在OS X上书面而是一个需要更换的所有实例\r的参数sed$(printf '\r')
abeboparebop

@abeboparebop:很棒的发现!👍或者,也可以使用自制软件安装GNU sed:stackoverflow.com/a/30005262
ssc

@abeboparebop,在OSX上,您只需要$在sed字符串之前添加,以防止将其转换\rr。简短示例:sed $'s/\r/~/'。完整示例:cat alpha.txt | tr '\n' '\r' | sed $'s/a test\rPlease do not/not a test\rBe/' | tr '\r' '\n'
wisbucky

40

考虑所有因素,将整个文件吞噬可能是最快的方法。

基本语法如下:

sed -e '1h;2,$H;$!d;g' -e 's/__YOUR_REGEX_GOES_HERE__...'

请注意,如果文件很大,则无法选择将整个文件都吞噬。对于这种情况,此处提供的其他答案提供了定制的解决方案,这些解决方案可保证在较小的内存空间上工作。

对于所有其他破解和斜线情况,只需在前面-e '1h;2,$H;$!d;g'加上原始sedregex参数即可完成工作。

例如

$ echo -e "Dog\nFox\nCat\nSnake\n" | sed -e '1h;2,$H;$!d;g' -re 's/([^\n]*)\n([^\n]*)\n/Quick \2\nLazy \1\n/g'
Quick Fox
Lazy Dog
Quick Snake
Lazy Cat

怎么-e '1h;2,$H;$!d;g'办?

12,$$!部分是行限定符的直接下面的命令运行在其上线该限制。

  • 1:仅第一行
  • 2,$:从第二行开始
  • $!:除最后一行外的所有行

如此扩展,这就是N行输入的每一行发生的情况。

  1: h, d
  2: H, d
  3: H, d
  .
  .
N-2: H, d
N-1: H, d
  N: H, g

g没有给该命令指定行指定符,但是前面的d命令有一个特殊的子句“ Start next cycle。 ”,这阻止g了除最后一行之外的所有行上运行。

至于每个命令的含义:

  • 第一h,接着H在每行拷贝据说输入的线成sed保留空间。(请考虑任意文本缓冲区。)
  • 然后,d丢弃每一行以防止这些行写入输出。在保持空间却得以保留。
  • 最后,在最后一行,g保持空间恢复每行的累积,以便sed能够在整个输入上运行其正则表达式(而不是以一次一行的方式),因此能够在上匹配\n

38

sed有三个命令来管理多线作战:NDP(他们比较正常 ndp)。

在这种情况下,您可以匹配模式的第一行,用于N将第二行追加到模式空间,然后用于s进行替换。

就像是:

/a test$/{
  N
  s/a test\nPlease do not/not a test\nBe/
}

2
这太棒了!比接受的答案更简单,并且仍然有效。
jeyk 2015年

而所有涉及的保留空间(那些GHx...)。也可以使用该s命令在模式空间中添加更多行。
斯特凡Chazelas


此解决方案不适用于以下情况:“这是\ na测试\ na测试\ n请不要\ n
发出警报

@ mug896您很可能需要多个N命令
loa_in_

15

您可以,但是很难。我建议切换到其他工具。如果有一个正则表达式与要替换的文本的任何部分都不匹配,则可以将其用作GNU awk中的awk记录分隔符。

awk -v RS='a' '{gsub(/hello/, "world"); print}'

如果您的搜索字符串中从来没有两个连续的换行符,则可以使用awk的“段落模式”(一个或多个空行分隔记录)。

awk -v RS='' '{gsub(/hello/, "world"); print}'

一个简单的解决方案是使用Perl并将文件完全加载到内存中。

perl -0777 -pe 's/hello/world/g'

1
如何将perl命令应用于文件?
sebix

2
@sebix perl -0777 -pe '…' <input-file >output-file。要修改文件,perl -0777 -i -pe '…' filename
Gilles

3
另请参见GNU sed-z选项(在2012年加入这个答案被张贴后): seq 10 | sed -z 's/4\n5/a\nb/'
斯特凡Chazelas

7

我认为这是2行匹配的sed解决方案。

sed -n '$!N;s@a test\nPlease do not@not a test\nBe@;P;D' alpha.txt

如果要匹配3行,则...

sed -n '1{$!N};$!N;s@aaa\nbbb\nccc@xxx\nyyy\nzzz@;P;D'

如果要匹配4行,则...

sed -n '1{$!N;$!N};$!N;s@ ... @ ... @;P;D'

如果“ s”命令中的替换部分缩小了行,则这样会更加复杂

# aaa\nbbb\nccc shrink to one line "xxx"

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@xxx@;$!N;$!N};P;D'

如果退役部分长出线,则这样会更复杂

# aaa\nbbb\nccc grow to five lines vvv\nwww\nxxx\nyyy\nzzz

sed -n '1{$!N};$!N;/aaa\nbbb\nccc/{s@@vvv\nwww\nxxx\nyyy\nzzz@;P;s/.*\n//M;P;s/.*\n//M};P;D'

这应该达到顶峰!我只是在两行替换中使用了“ -i”而不是“ -n”,因为这是我所需要的,顺便说一句,这也在asker的示例中。
纳吉夫

5
sed -i'.original' '/a test/,/Please do not/c not a test \nBe' alpha.txt

这里/a test/,/Please do not/被视为(多行)文本块,c更改命令后跟新文本not a test \nBe

在要替换的文本很长的情况下,我建议使用ex语法。


糟糕的问题是sed会替换/ a test /和//请不要/之间的所有最终文本... :(
noonex

4
sed -e'$!N;s/^\(a test\n\)Please do not be$/not \1Be/;P;D' <in >out

只需稍微扩大一下输入范围即可。

很简单 除了标准替代;你只需要$!NP以及D在这里。


4

除了Perl之外,用于流(和文件)的多行编辑的一种通用且方便的方法是:

首先,根据需要创建一些新的UNIQUE行分隔符

$ S=__ABC__                     # simple
$ S=__$RANDOM$RANDOM$RANDOM__   # better
$ S=$(openssl rand -hex 16)     # ultimate

然后在sed命令(或任何其他工具)中,将\ n替换为$ {S},例如

$ cat file.txt | awk 1 ORS=$S |  sed -e "s/a test${S}Please do not/not a test\nBe/" | awk 1 RS=$S > file_new.txt

(awk用您的替换ASCII行分隔符,反之亦然。)


2

这是对xara的聪明回答的一个小修改,使其可以在OS X上运行(我使用的是10.10):

cat alpha.txt | tr '\n' '\r' | sed -e 's/a test$(printf '\r')Please do not/not a test$(printf '\r')Be/'  | tr '\r' '\n'

不必显式使用\r,而必须使用$(printf '\r')


1
尽管printf '\r'(或echo -e '\r')可以正常工作,但是请注意,您可以仅使用Shell语法$'\r'来引用转义文字。例如,echo hi$'\n'there将在hi和之间回显换行符there。同样,您可以包装整个字符串,以便每个反斜杠\ 都可以转义其后续字符:echo $'hi\nthere'
Dejay Clayton,

1

我想使用sed向文件中添加几行HTML(并在此处结束)。通常我只使用perl,但我当时使用的是sed,bash和其他功能。我发现,如果我将字符串更改为单行,然后让bash / sed插值\ t \ n,那么一切都会完成:

HTML_FILE='a.html' #contains an anchor in the form <a name="nchor" />
BASH_STRING_A='apples'
BASH_STRING_B='bananas'
INSERT="\t<li>$BASH_STRING_A<\/li>\n\t<li>$BASH_STRING_B<\/li>\n<a name=\"nchor\"\/>"
sed -i "s/<a name=\"nchor"\/>/$INSERT/" $HTML_FILE

具有转义双引号和正斜杠的功能会更清洁,但有时抽象是时间的窃贼。


1

GNU sed有一个-z选项,允许使用OP尝试应用的语法。(手册页

例:

$ cat alpha.txt
This is
a test
Please do not
be alarmed
$ sed -z 's/a test\nPlease do not\nbe/not a test\nBe/' -i alpha.txt
$ cat alpha.txt
This is
not a test
Be alarmed

请注意:如果使用^$它们现在与以NUL字符(不是\n)分隔的行的开头和结尾匹配。并且,要确保\n替换所有(- 分隔的)行上的匹配项,请不要忘记使用该g标志进行全局替换(例如s/.../.../g)。


鸣谢: @stéphane-chazelas 在上面的评论中首先提到了-z。


0

Sed在换行符上中断输入。每个循环仅保留一行。
因此,\n如果模式空间不包含(换行符),则无法匹配它。

但是,有一种方法可以使sed 通过使用循环在模式空间中保留两个连续的行:

sed 'N;l;P;D' alpha.txt

在N和P之间添加所需的所有处理(替换l)。

在这种情况下(2行):

$ sed 'N;s/a test\nPlease do not/not a test\nBe/;P;D' alpha.txt
This is
not a test
Be
be alarmed

或者,对于三行:

$ sed -n '1{$!N};$!N;s@a test\nPlease do not\nbe@not a test\nDo\nBe@;P;D' alpha.txt 
This is
not a test
Do
Be alarmed

假设替换了相同数量的行。

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.