【问题标题】:Replace delimited block of text in file with the contents of another file用另一个文件的内容替换文件中的分隔文本块
【发布时间】:2011-02-11 13:58:24
【问题描述】:

我需要编写一个简单的脚本来将配置文件中的一段文本替换为另一个文件的内容。

假设有以下简化文件:

server.xml

<?xml version='1.0' encoding='UTF-8'?>
<Server port="8005" shutdown="SHUTDOWN">
  <Service name="Catalina">
    <Connector port="80" protocol="HTTP/1.1"/>
    <Engine name="Catalina" defaultHost="localhost">
      <!-- BEGIN realm -->
        <sometags/>
        <sometags/>
      <!-- END realm -->
      <Host name="localhost" appBase="webapps"/>
    </Engine>
  </Service>
</Server>

realm.xml

<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
       resourceName="UserDatabase"/>

我想运行一个脚本并让realm.xml 替换&lt;!-- BEGIN realm --&gt;&lt;!-- END realm --&gt; 行之间的内容。如果realm.xml 发生变化,那么每当脚本再次运行时,它将再次用realm.xml 的新内容替换这些行。这旨在在领域将不同的多个安装上的服务启动时在/etc/init.d/tomcat 中运行。

我不太确定如何简单地使用 awksed 来做到这一点。

【问题讨论】:

    标签: bash shell scripting sed awk


    【解决方案1】:

    我无法在 OS X 上轻松获得 Dennis 解决方案(它的 BSD sed 略有不同)。我发现了另一个我能够在 Linux 和 OS X 上运行的解决方案(我有一个混合环境)。 superuser.com 上的原始版本仅适用于 Linux,我在这里修复了它:

    lead='^<!-- BEGIN realm -->$'
    tail='^<!-- END realm -->'
    sed  -e '/'"$lead"'/,/'"$tail"'/{ /'"$lead"'/{p; r realm.xml' -e' }; /'"$tail"'/p; d;} '  server.xml
    

    这里的 Dennis 代码版本也适用于 OS X(使用多行):

    sed -ne '/'"$lead"'/ {
     p
     r realm.xml
     :a
     n 
     /'"$tail"'/ {
      p
      b
     } 
     ba
     }
    p' server.xml
    

    这两个代码都在标准输出上打印输出。使用重定向,或者,为了替换文件内联,添加选项 '-i'(在 linux 上)或 '-i ""'(在 BSD/OS X 上)。

    【讨论】:

      【解决方案2】:

      我遇到了同样的需求(因此找到了这个问题)。在使用 sed 和 awk 太久之后,我最终意识到使用像 Python 这样的现代、可读、可理解、广泛可用的语言并没有错:

          python <<EOF
          import os, sys, re
          fname = 'server.xml'
          os.rename(fname, fname + '.orig')
          with open(fname + '.orig', 'r') as fin, open(fname, 'w') as fout:
              data = fin.read()
      
              data = re.sub(r'(<!-- BEGIN realm -->).*?(<!-- END realm -->)', 
                r'\1\n' +
                'insert whatever you want here\n' + 
                r'\2\n', data, flags=re.DOTALL)
              fout.write(data)
          EOF
      

      我认为 sed 和 awk 已经度过了他们的一天。它们曾经很有用,但现在很少有人可以在没有文档帮助的情况下阅读或写作。

      (来源:互联网)

      【讨论】:

        【解决方案3】:

        试试这个:

        sed -i -ne '/<!-- BEGIN realm -->/ {p; r realm.xml' -e ':a; n; /<!-- END realm -->/ {p; b}; ba}; p' server.xml
        

        【讨论】:

        • 哇...它的工作原理。我正在尝试掌握分支以真正了解发生了什么。
        • ba 分支到与“BEGIN”测试关联的大括号内标记“a”,当找到“END”时,b 分支到末尾,因为它位于一组与该测试相关的大括号。有点像if /BEGIN/ then read file; while not /END/ do skip line
        • 我收到一个语法错误:sed: -e expression #1, char 39: unexpected }'`
        • @SteveBennett:-i 需要独立。折叠时(如在-ine 中),它会将ne 视为备份文件的后缀,并且无法看到导致错误的第一个-e 子句。我已经更正了我的答案。
        • 像一个魅力一样工作,如果你有一个变量保存文件名和要读取的文本作为单引号禁用外壳扩展,则只需使用双引号。
        【解决方案4】:
        TOTAL_LINES=`cat server.xml | wc -l`
        BEGIN_LINE=`grep -n -e '<!-- BEGIN realm -->' server.xml | cut -d : -f 1`
        END_LINE=`grep -n -e '<!-- END realm -->' server.xml | cut -d : -f 1`
        TAIL_LINES=$(($TOTAL_LINES-$END_LINE))
        
        head -n $BEGIN_LINE server.xml > server2.xml
        cat realm.xml > server2.xml
        tail -n $TAIL_LINES server.xml > server2.xml
        

        (好的,这不使用 awk 或 sed...我认为这不是排他性要求 :-)

        【讨论】:

        • 这不是排他性要求 ;-)
        • 这行得通吗?在大多数 wc 版本中,TOTAL_LINES 的值将包含字符串“server.xml”,所以我怀疑算术会失败。
        【解决方案5】:

        您也可以使用 ed 命令(参见http://wiki.bash-hackers.org/howto/edit-ed):

        cat <<-'EOF' | sed -e 's/^ *//' -e 's/ *$//' | ed -s server.xml
           H
           /BEGIN realm/i
           .
           /BEGIN realm/+1,/END realm/-1d
           .-1r realm.xml
           wq
        EOF
        

        【讨论】:

          【解决方案6】:

          我创建的这个小sn-p怎么样:

          sed -n \
            -e "1,/<\!-- BEGIN realm -->/ p" \
            -e"/<\!-- END realm -->/,$ p" \
            -e "/<\!-- BEGIN realm -->/ r realm.xml" \
            server.xml
          

          第一个命令打印直到&lt;!- BEGIN realm --&gt; 的行,第二个命令打印从&lt;!-- END realm --&gt; 开始的行,第三个命令将文本附加到文件“realm.xml”中。如果我可以简化删除&lt;!- BEGIN realm --&gt;&lt;!-- END realm --&gt; 之间的线而不删除标记线,它会尽可能简单。并且可以使用 sed inplace 完成!!!

          【讨论】:

          • &lt;sometags/&gt; 呢?您的 sed 命令不会替换 &lt;sometags/&gt;
          • 当我在我的 linux 机器上运行它时。此外,如果您在没有最后一个脚本 (-e) 的情况下运行命令,它会给出 server.xml 而没有所有 &lt;sometags/&gt;
          • 在 Ubuntu Precise 上对我不起作用。插入文本但不删除 ...
          【解决方案7】:

          你可以使用 awk

          awk 'FNR==NR{ _[++d]=$0;next}
          /BEGIN realm/{
            print
            for(i=1;i<=d;i++){ print _[i] }
            f=1;next
          }
          /END realm/{f=0}!f' realm.xml server.xml > temp && mv temp server.xml
          

          realm.xml 作为第一个文件传递给 awk。 FNR==NR 表示获取传入的第一个文件的记录并存储到变量_。一旦 FNR!=NR,awk 将处理下一个文件。如果 awk 找到 /BEGIN realm/,则打印 BEGIN realm 行,然后打印存储在 _ 中的内容。通过将标志 (f) 设置为 1,BEGIN realm 之后的其余行将在检测到 /END realm/ 之前不会被打印。

          【讨论】:

          • 这似乎是正确的方法,但它非常神秘。您能否提供一些有关其工作原理的线索?
          • 如何改变它以便它可以像“sed -i”一样就地替换?
          • 您只需要重定向到临时文件并将其重命名即可。
          猜你喜欢
          • 1970-01-01
          • 2017-05-25
          • 1970-01-01
          • 1970-01-01
          • 2020-01-09
          • 1970-01-01
          • 2018-03-29
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多