【问题标题】:awk : parse and write to another fileawk : 解析并写入另一个文件
【发布时间】:2016-05-14 01:08:36
【问题描述】:

我在 XML 文件中有如下记录。我需要搜索<keyword>SEARCH</keyword>,如果存在 那么我需要将整个记录写入另一个文件。(从<record>开始到</record>

下面是我在循环中的 awk 代码。 $1 保存每条记录的行值。

if(index($1,"SEARCH")>0)
{
print $1>> "output.txt"
}

这个逻辑有两个问题,

  1. 正在写入output.txt 文件,仅写入<keyword>SEARCH</keyword> 元素而不是整个记录(从<record> 开始到</record>
  2. SEARCH 也可以出现在<detail> 标记中。这段代码甚至会将该标签写入output.txt

XML 文件:

<record category="xyz">
<person ssn="" e-i="E">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<names>
<first_name/>
<last_name></last_name>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>SEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is present in abc for xyz reason</detail>
</external_sources>
</details>
</record>
<record category="abc">
<person ssn="" e-i="F">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<names>
<first_name/>
<last_name></last_name>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>DONTSEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is not present in abc for xyz reason</detail>
</external_sources>
</details>
</record>

【问题讨论】:

  • 使用需要使用XML解析器来解析XML。任何文本处理解决方案都是脆弱的。

标签: shell unix awk scripting


【解决方案1】:
$ cat x.awk
/<record / { i=1 }
i { a[i++]=$0 }
/<\/record>/ {
    if (found) {
        for (i=1; i<=length(a); ++i) print a[i] > "output.txt"
    }
    i=0;
    found=0
}
/<keyword>SEARCH<\/keyword>/ { found=1 }


$ awk -f x.awk x.xml

$ cat output.txt
<record category="xyz">
<person ssn="" e-i="E">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<names>
<first_name/>
<last_name></last_name>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>SEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is present in abc for xyz reason</detail>
</external_sources>
</details>
</record>

【讨论】:

  • 如果我的搜索关键字不止一个?喜欢&lt;keyword&gt;SEARCH1&lt;/keyword&gt;&lt;keyword&gt;SEARCH2&lt;/keyword&gt;。如果其中任何一个匹配,则应将整个记录写入output.txt
  • 一种方法是使用|| 表示“或”条件,例如/&lt;keyword&gt;SEARCH1&lt;\/keyword&gt;/ || /&lt;keyword&gt;SEARCH2&lt;\/keyword&gt;/ { found=1 }。或者您可以列出多个模式,每个模式都有自己的 { found = 1} 块。
  • 谢谢。如果我已将所有 SEARCH 关键字存储在一个数组中,我该怎么办。例如,keywordArray variable holds SEARCH1,SEARCH2,SEARCH3 etc。上述 OR 条件中的语法应该是什么?
  • 在这种情况下,您可以在代码块中放置一个循环来测试数组的每个元素,而不是一个模式。类似{ for (s in a) { f="&lt;keyword&gt;"s"&lt;/keyword&gt;"; if ($0 ~ f) found = 1; }}
  • 我将 /&lt;keyword&gt;SEARCH&lt;\/keyword&gt;/ { found=1 } 更改为 $0 ~ "readSearchKeywordFromCommandLine" {found=1} 。在这种情况下,上面的代码不匹配。详情请参考question ..
【解决方案2】:

您似乎已经从Unix & Linux 交叉发布了这个问题 - 我在这里给出的答案与我在那里所做的相同:

我将假设您发布的内容是一个示例,因为它不是有效的 XML。如果这个假设不成立,我的回答就不成立……但如果是这样的话,你真的需要用一份 XML 规范的卷起副本来打击给你 XML 的人,并要求他们'修复它。

但实际上 - awk 和正则表达式并不是适合这项工作的工具。 XML 解析器是。使用解析器,做你想做的事非常简单:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig; 

#parse your file - this will error if it's invalid. 
my $twig = XML::Twig -> new -> parsefile ( 'your_xml' );
#set output format. Optional. 
$twig -> set_pretty_print('indented_a');

#iterate all the 'record' nodes off the root. 
foreach my $record ( $twig -> get_xpath ( './record' ) ) {
   #if - beneath this record - we have a node anywhere (that's what // means)
   #with a tag of 'keyword' and content of 'SEARCH' 
   #print the whole record. 
   if ( $record -> get_xpath ( './/keyword[string()="SEARCH"]' ) ) {
       $record -> print;
   }
}

xpath 很像正则表达式——在某些方面——但它更像是一个目录路径。这意味着它是上下文感知的,并且可以处理 XML 结构。

在上面:./ 表示“低于当前节点”,所以:

$twig -> get_xpath ( './record' )

表示任何“顶级”&lt;record&gt; 标签。

但是.// 的意思是“在任何级别,低于当前节点”,所以它会递归地执行它。

$twig -> get_xpath ( './/search' ) 

将获得任何级别的任何&lt;search&gt; 节点。

方括号表示一个条件——要么是一个函数(例如text() 获取节点的文本),要么你可以使用一个属性。例如//category[@name] 会找到任何具有 name 属性的类别,//category[@name="xyz"] 会进一步过滤这些类别。

用于测试的 XML:

<XML>
<record category="xyz">
<person ssn="" e-i="E">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<details>
<names>
<first_name/>
<last_name></last_name>
</names>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>SEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is present in abc for xyz reason</detail>
</external_sources>
</details>
</person>
</record>
<record category="abc">
<person ssn="" e-i="F">
<title xsi:nil="true"/>
<position xsi:nil="true"/>
<details>
<names>
<first_name/>
<last_name></last_name>
</names>
<aliases>
<alias>CDP</alias>
</aliases>
<keywords>
<keyword xsi:nil="true"/>
<keyword>DONTSEARCH</keyword>
</keywords>
<external_sources>
<uri>http://www.google.com</uri>
<detail>SEARCH is not present in abc for xyz reason</detail>
</external_sources>
</details>
</person>
</record>
</XML>

输出:

 <record category="xyz">
    <person
        e-i="E"
        ssn="">
      <title xsi:nil="true" />
      <position xsi:nil="true" />
      <details>
        <names>
          <first_name/>
          <last_name></last_name>
        </names>
        <aliases>
          <alias>CDP</alias>
        </aliases>
        <keywords>
          <keyword xsi:nil="true" />
          <keyword>SEARCH</keyword>
        </keywords>
        <external_sources>
          <uri>http://www.google.com</uri>
          <detail>SEARCH is present in abc for xyz reason</detail>
        </external_sources>
      </details>
    </person>
  </record>

注意 - 以上只是将记录打印到 STDOUT。这实际上......在我看来,这不是一个好主意。尤其是因为 - 它不打印 XML 结构,因此如果您有多个记录(没有“根”节点),它实际上不是“有效”XML。

所以我会改为 - 完全按照您的要求完成:

#!/usr/bin/env perl

use strict;
use warnings;

use XML::Twig; 

my $twig = XML::Twig -> new -> parsefile ('your_file.xml'); 
$twig -> set_pretty_print('indented_a');

foreach my $record ( $twig -> get_xpath ( './record' ) ) {
   if ( not $record -> findnodes ( './/keyword[string()="SEARCH"]' ) ) {
       $record -> delete;
   }
}

open ( my $output, '>', "output.txt" ) or die $!;
print {$output} $twig -> sprint;
close ( $output ); 

这相反 - 反转逻辑,并删除(从内存中已解析的数据结构中)您想要的记录,并将整个新结构(包括 XML 标头)打印到新的名为“output.txt”的文件。

【讨论】:

    【解决方案3】:

    对多字符 RS 使用 GNU awk:

    $ awk -v RS='</record>\n' '{ORS=RT} /<keyword>SEARCH<\/keyword>/' file 
    <record category="xyz">
    <person ssn="" e-i="E">
    <title xsi:nil="true"/>
    <position xsi:nil="true"/>
    <names>
    <first_name/>
    <last_name></last_name>
    <aliases>
    <alias>CDP</alias>
    </aliases>
    <keywords>
    <keyword xsi:nil="true"/>
    <keyword>SEARCH</keyword>
    </keywords>
    <external_sources>
    <uri>http://www.google.com</uri>
    <detail>SEARCH is present in abc for xyz reason</detail>
    </external_sources>
    </details>
    </record>
    

    如果您需要搜索多个关键字中的任何一个,那么只需将它们列出:

    $ awk -v RS='</record>\n' '{ORS=RT} /<keyword>(SEARCH1|SEARCH2|SEARCH3)<\/keyword>/' file 
    

    【讨论】:

      猜你喜欢
      • 2015-05-14
      • 1970-01-01
      • 2016-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-10
      • 2015-05-24
      • 1970-01-01
      相关资源
      最近更新 更多