【问题标题】:How to use sed to replace multiline string?如何使用 sed 替换多行字符串?
【发布时间】:2016-06-14 02:13:18
【问题描述】:

如何使用 bash sed 命令更改此字符串:

<Directory /var/www/>
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

到下面的字符串? (只改变字符串的第 3 行)

<Directory /var/www/>
    Options Indexes FollowSymLinks
    AllowOverride All
    Require all granted
</Directory>

注意 1: 我不只是想定位字符串 'AllowOverride None',因为文件中还有其他不应更改的事件。我需要定位以&lt;Directory /var/www&gt;开头的整个字符串

注意 2: 我还需要覆盖文件。所以,在你的回答中考虑到这一点。并为 sed 的 GNU/非 GNU 版本提供不同版本以防万一。

【问题讨论】:

  • 最好有一个更通用的minimal reproducible example 来查看什么是不变的,什么不是:它必须在/var/www 上吗?此外,将sed 用于多行似乎不是最干净的方式:awk 可能可以更好地处理这种方式。

标签: sed


【解决方案1】:

由于模式包含斜线,因此使用\%(对于任何字符%)来标记搜索模式。然后使用:

sed -e '\%^<Directory /var/www/>%,\%^</Directory>% s/AllowOverride None/AllowOverride All/'

\%…% 内的搜索模式将搜索限制在匹配模式之间的行,{ s/…/…/; } 在该范围内查找所需的模式并进行适当的替换。

如果您不想将其限制为单个目录部分而是所有目录部分,请适当调整启动模式。例如,这将匹配任何 &lt;Directory&gt; 部分:

sed -e '\%^<Directory [^>]*>%,\%^</Directory>% s/AllowOverride None/AllowOverride All/'

您可以根据自己的要求使其更具选择性。

【讨论】:

  • 我不认为 OP 打算将替换限制在一个 部分(而是所有 部分)。此外,我认为,此解决方案仍将替换(第一个) 和最后一次出现的 之间所有出现的 AllowOverride None 标签。
  • @Leon:如果目录名称无关紧要,请将其从搜索范围的第一个模式中删除。否:它将分别处理每个 &lt;Directory ...&gt;&lt;/Directory&gt; 范围;它不会使用第一个 &lt;Directory 和最后一个 &lt;/Directory&gt; 作为单个范围。
  • 是的,你是对的。我在最初的版本中有一个错字,就是这样尝试的!
  • @JonathanLeffler 该模式效果很好,但我如何使用它来覆盖原始文件?尝试添加 -i 但一无所获。
  • 使用sed -i.bak -e '…' file.txt。用&gt; file.txt 覆盖输入文件是一种创建空文件(或删除以前的非空文件)的昂贵方法。
【解决方案2】:

简单的版本,依靠 之后两行内的 AllowOverride 行并使用 GNU sed 扩展,是这样的:

sed '/^<Directory/,+2 { s/AllowOverride None/AllowOverride All/g; }'

更新:这是不依赖任何 GNU 扩展的版本(我先尝试过,但打错了字,很惊讶它不起作用,这就是为什么先发布另一个版本的原因):

sed '/^<Directory/,/^<\/Directory>/ { s/AllowOverride None/AllowOverride All/; }'

【讨论】:

    【解决方案3】:

    我意识到这不是您所要求的,也许不使用 sed 值得吗?

    python 解决方案怎么样?它会将目录作为第一个参数传递给脚本并替换 exactly &lt;Directory 元素,而 onlyNone 更改为 All 并将更改写回文件。它还可以使用不同的缩进级别,同时保留原始缩进。适用于 python2 和 python3。

    毕竟我假设如果你有 sed 你可能也有 python。

    #!/usr/bin/env python
    import re
    
    r = re.compile(r'(<Directory /var/www/>\s+Options Indexes FollowSymLinks\s+AllowOverride )None(\s+Require all granted\s+</Directory>)', re.MULTILINE)
    
    for root, dirs, files in os.walk(sys.argv[1]):
        for file_name in files:
            if file_name.endswith('.conf'):
                file_path = os.path.join(root, file_name)
                with open(file_path) as fp:
                    data = r.sub(r'\1All\2', fp.read())
                with open(file_path, 'w+') as fp:
                    fp.write(data)
    

    【讨论】:

      【解决方案4】:

      使用 Gnu Sed:

       sed -zie 's!\(<Directory /var/www/>[^<]*AllowOverride\) None!\1 All!'  ex1.txt
      
      • 选项-z用于Null分隔记录:所有文件都是一条记录, 所以只需做一个简单的替换。
      • [^&lt;]*(多行)正则表达式尊重目录边界,并允许灵活的格式和顺序。

      【讨论】:

        【解决方案5】:

        您的问题很好地说明了口头禅,不要使用 sed。确实,您不应该将任何正则表达式引擎用于像 XML 这样的无上下文语言。但是您可以使用 awk 接近,也许足够接近。

        #! /usr/bin/awk -f
        
        /<Directory \/var\/www\/>/ {
            line = NR
        }
        
        /    AllowOverride None/ && line + 2 == NR {
            gsub( /None/, "All" )
        }
        
        { print }
        

        这样您就无需阅读任何花哨的非标准正则表达式,并且您的代码准确地说明了它的含义:如果您在“目录”行之后发现“AllowOverride”2 行,请将其替换。上面的正则表达式都非常简单(并且符合 Posix),并且应该适用于任何版本的 awk。

        【讨论】:

          【解决方案6】:

          这个user已经给出了你的答案,只是check here

          一些参考

          在最简单的 sed 调用中,它在模式空间中有一行文本,即。输入中的 1 行 \n 分隔文本。模式空间中的单行没有 \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
          

          这是我的命令“备忘单”

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-04-30
            • 2016-06-07
            • 1970-01-01
            • 2021-03-01
            • 1970-01-01
            • 1970-01-01
            • 2023-03-19
            相关资源
            最近更新 更多