【问题标题】:How to use awk to insert multiple lines after first match of a pattern, in multiple files如何在多个文件中第一次匹配模式后使用 awk 插入多行
【发布时间】:2018-12-05 12:05:23
【问题描述】:

我有一个包含许多子目录的目录,每个子目录都包含一个我要编辑的config.xml 文件。喜欢:

../jobs/foo_bar-v1.2_west/config.xml
../jobs/foo_bar-v1.3_west/config.xml
../jobs/foo_stuff-v1.3_east/config.xml
../jobs/foo_foo-v9.8_north/config.xml
../jobs/NOT_FOO-v0.1_whatev/config.xml
etc.

在匹配特定行的第一个实例<properties> 之后,我需要一种方法将多行文本插入到多个../jobs/foo*/config.xml 文件中。

要插入的文本如下所示:

    <a.bunch.of.TextGoesHere>
      <permission>one.foo.Items.Foo:person.name</permission>
      <permission>two.foo.Items.Foo:person.name</permission>
      <permission>three.foo.Items.Foo:person.name</permission>
    </a.bunch.of.TextGoesHere>

每个../jobs/foo*/config.xml 看起来像:

<?xml version='1.0' encoding='UTF-8'?>
<foo1>
  <actions/>
  <description>foo2</description>
  <keepDependencies>false</keepDependencies>
  <properties>
    <foo3/>
  </properties>
 ...
  <lots_of_other_stuff>
  <properties>
    <junk>
  </properties>

每个config.xml 的最终输出应如下所示:

<?xml version='1.0' encoding='UTF-8'?>
<foo1>
  <actions/>
  <description>foo2</description>
  <keepDependencies>false</keepDependencies>
  <properties>
    <a.bunch.of.TextGoesHere>
      <permission>one.foo.Items.Foo:person.name</permission>
      <permission>two.foo.Items.Foo:person.name</permission>
      <permission>three.foo.Items.Foo:person.name</permission>
    </a.bunch.of.TextGoesHere>
    <foo3/>
  </properties>
 ...
  <lots_of_other_stuff>
  <properties>
    <junk>
  </properties>

我尝试使用sed 在特定行之后插入,例如

#!/bin/bash
find ../jobs/run* -name config.xml -exec sed -i '6a\
<text to insert>' {} \;

但偶尔,config.xml 中的长 &lt;description&gt; 文本会导致插入不可预测的行号。

接下来我尝试使用sed 搜索&lt;properties&gt; 的第一个实例并在之后插入,例如

sed -i '0,/properties/a test' config.xml

但这导致在每一行之后添加test 测试,直到找到&lt;properties&gt;。使用sed -i '1,/ 有类似的结果。太丑了。

我不确定我是否在这个 Amazon Linux 机器上正确使用了 sed,并且我认为 awk 在这里可能会更好。有人可以帮忙吗?谢谢。

【问题讨论】:

标签: xml linux bash awk sed


【解决方案1】:

假设要插入的文本在一个名为insert的文件中:

sed -e '0,/<properties>/{/<properties>/r insert' -e '}' config.xml

r 命令读取一个文件并将其附加到当前行之后;

0,/pattern/{/pattern/r filename}

确保只有pattern 的第一个实例获得附加文本。因为命令必须在r读取的文件名之后结束,所以必须使用-e将它分成两部分。

要就地编辑文件,请使用sed -i(适用于 GNU sed)。

要对多个文件执行此操作,您可以使用 find:

find jobs -name 'config.xml' \
    -exec sed -i -e '0,/<properties>/{/<properties>/r insert' -e '}' {} +

这要求insert 文件位于您运行此命令的目录中。


您的命令似乎几乎是正确的,只是您没有在范围内嵌套第二个地址以确保附加只发生一次。

【讨论】:

  • 哦,是的,自动打印好多了。处理直到第一个匹配,只对匹配的行执行,其余的按原样传递。我可能仍然会选择a 不需要额外的文件。
  • @OndrejK。是的,ar 可能更方便,具体取决于提供要插入的文本的方式。
  • 谢谢你,@BenjaminW.,这在没有太多改动的情况下工作得非常完美。脚本最终看起来像:find ../jobs/foo* -name 'config.xml' -exec sed -i -e '0,/&lt;properties&gt;/{/&lt;properties&gt;/r insert.txt' -e '}' '{}' \; 感谢您的帮助!
【解决方案2】:

跟进我的评论并给出答案:

输入的xml文件“file.xml”

<?xml version='1.0' encoding='UTF-8'?>
<foo1>
  <actions/>
  <description>foo2</description>
  <keepDependencies>false</keepDependencies>
  <properties>
    <foo3/>
  </properties>
 ...
  <lots_of_other_stuff />
  <properties>
    <junk />
  </properties>
</foo1>

xslt 样式表“file.xslt”

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <!-- Identity transform -->
    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()"/>
        </xsl:copy>
    </xsl:template>
    <!-- insert the new stuff before the first child of the first properties element -->
    <xsl:template match="/foo1/properties[1]/*[1]">
        <a.bunch.of.TextGoesHere>
            <permission>one.foo.Items.Foo:person.name</permission>
            <permission>two.foo.Items.Foo:person.name</permission>
            <permission>three.foo.Items.Foo:person.name</permission>
        </a.bunch.of.TextGoesHere>
        <xsl:copy-of select="."/>
   </xsl:template>
</xsl:stylesheet>

结果,使用

$ xmlstarlet transform file.xslt file.xml 
<?xml version="1.0"?>
<foo1>
  <actions/>
  <description>foo2</description>
  <keepDependencies>false</keepDependencies>
  <properties>
    <a.bunch.of.TextGoesHere><permission>one.foo.Items.Foo:person.name</permission><permission>two.foo.Items.Foo:person.name</permission><permission>three.foo.Items.Foo:person.name</permission></a.bunch.of.TextGoesHere><foo3/>
  </properties>
 ...
  <lots_of_other_stuff/>
  <properties>
    <junk/>
  </properties>
</foo1>

应用到您的所有文件:

find . -name config.xml -exec sh -c '
    for xmlfile; do
        xmlstarlet transform xform.xslt "$xmlfile" > "$xmlfile".new &&
        ln "$xmlfile" "$xmlfile".bak &&
        mv "$xmlfile".new "$xmlfile"
    done
' sh {} +

【讨论】:

  • 我不确定 xmlstarlet 对该输出的格式做了什么。
  • 感谢您的回复,但我最终使用了上面 Benjamin W. 的 sed 行。我想避免在我正在处理的盒子上安装 xmlstarlet 或其他任何东西。
【解决方案3】:

使用 GNU awk 进行就地编辑,您只需要:

awk -i inplace '
NR==FNR { text = (NR>1 ? text ORS : "") $0 }
FNR==1 { cnt=0 }
{ print }
/<properties>/ && !cnt++ { print text }
' file_containing_text_to_insert ../jobs/foo*/config.xml 

【讨论】:

  • 感谢您的回复。这几乎可以工作。不幸的是,它获取file_containing_text_to_insert 的内容并将其插入:A)一次在文件的最顶部,B)一次,在上面, 的每个实例。此外,它只是将输出打印到我的终端,不会将输出保存到每个文件。
  • 我忘记添加 -i inplace 标志,我的打印位置错误,我没有注意到您只想在 first 出现之后添加新文本每个文件中的属性。请现在再试一次。
猜你喜欢
  • 1970-01-01
  • 2013-11-25
  • 2014-09-16
  • 1970-01-01
  • 2021-10-28
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
  • 2018-06-27
相关资源
最近更新 更多