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

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

我注意到,如果我添加\n一个模式来替换 using 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

我怎样才能让它发挥作用?

答案1

在最简单的调用中sed,它有模式空间中的文本行,即。\n来自输入的1 行分隔文本。模式空间中的单行没有\n...这就是为什么你的正则表达式没有找到任何东西。

您可以将多行读入模式空间并令人惊讶地很好地操作事物,但是需要付出比平常更多的努力。Sed 有一组允许这种类型的事情的命令...这里是一个链接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

这是我的命令“cheat-sheet”

:  # 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                   

答案2

使用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 读取整个文件。看佩尔多克佩尔伦了解更多相关信息。

答案3

我认为,最好将\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'

如果有人不知道,\nUNIX 行结尾是\r\n- windows,\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'

答案4

所有的情况都被考虑到了,吞噬整个文件可能是最快的方法。

基本语法如下:

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

请注意,如果文件非常大,则吞掉整个文件可能不是一个选择。对于这种情况,此处提供的其他答案提供了保证在较小内存占用上工作的定制解决方案。

对于所有其他的砍杀情况,只需-e '1h;2,$H;$!d;g'在前面加上原始sed正则表达式参数就可以完成工作。

例如

$ 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'作用?

1, 2,$,部分$!是行说明符,用于限制紧随其后的命令在哪些行上运行。

  • 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命令有一个特殊子句“开始下一个周期。",这会阻止g在除最后一行之外的所有行上运行。

至于各个命令的含义:

  • 每行第一个h后跟Hs 的行将输入的所述行复制到seds中保留空间。 (想想任意文本缓冲区。)
  • 然后,d丢弃每一行以防止这些行被写入输出。这保留空间但仍被保留。
  • 最后,在最后一行,g恢复从保留空间这样就sed能够在整个输入上运行其正则表达式(而不是以一次一行的方式),因此能够在\ns 上进行匹配。

相关内容