shell 用另一个文件的内容替换文件中分隔的文本块

zd287kbt  于 12个月前  发布在  Shell
关注(0)|答案(8)|浏览(135)

我需要编写一个简单的脚本,用另一个文件的内容替换配置文件中的文本块。
让我们假设有以下简化的文件:
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替换<!-- BEGIN realm --><!-- END realm -->行之间的内容。如果realm.xml发生了变化,那么每当脚本再次运行时,它将再次用realm.xml的新内容替换这些行。这是为了在/etc/init.d/tomcat中运行,在多个安装上启动服务时,域将是不同的。
我不太确定如何简单地使用awksed来实现这一点。

hmmo2u0o

hmmo2u0o1#

给予这个试试:

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

vi4fp9gy2#

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

(OK,这不使用awk或sed...我认为这不是一个唯一的要求:-)

pgpifvop

pgpifvop3#

可以使用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表示获取传入的第一个文件的记录并存储到变量_。awk将处理下一个文件一次FNR!=NR.如果awk找到/BEGIN realm/,则打印BEGIN realm行,然后打印_中存储的内容。通过将标志(f)设置为1,将不会打印BEGIN realm之后的其余行,直到检测到/END realm/

qlfbtfca

qlfbtfca4#

我创建的这个小片段怎么样:

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

第一个命令打印最多<!- BEGIN realm -->的行,第二个命令打印从<!-- END realm -->开始的行,第三个命令将文本附加到文件'realm.xml'中。如果我能简化<!- BEGIN realm --><!-- END realm -->之间的线的删除,而不删除标记线,那就太简单了。而且它可以用sed完成inplace!!!

r1zk6ea1

r1zk6ea15#

您还可以使用艾德命令(参见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
4dc9hkyq

4dc9hkyq6#

我遇到了同样的需求(因此找到了这个问题)。在使用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已经风光不再了。它们曾经很有用,但现在很少有人能在没有文件帮助的情况下阅读或写作。
(来源:互联网)

dy1byipe

dy1byipe7#

我无法让Dennis解决方案在OSX上轻松工作(它的BSD sed略有不同)。我发现了另一个解决方案,我能够在Linux和OSX上工作(我有一个混合环境)。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

这两个代码都在stdout上打印输出。使用重定向,或者添加选项'-i'(在Linux上)或'-i“"'(在BSD/OS X上)以内联方式替换文件。

xjreopfe

xjreopfe8#

简单的sed和稍微复杂一点的awk解决方案:

sed '
    /<!-- END realm/,$b
    /<!-- BEGIN realm/r realm.xml
    1,//!d
' server.xml
  • 一旦看到结束线,只需将线穿过
  • 当看到开始行时,追加新内容
  • 如果超出开始行(但不是结束行),则删除
awk '
    NR==FNR { new = new $0 RS; next }
    /<!-- END realm -->/ { $0 = new $0; skip = 0 }
    !skip
    /<!-- BEGIN realm -->/ { skip = 1 }
' realm.xml server.xml
  • skip开始未设置
  • 加载新内容
  • 当我们看到结束行时,添加新内容并更改状态(unset skip
  • 如果未设置skip,则打印行
  • 当看到开始行时,改变状态(设置skip

相关问题