【问题标题】:Shell scripting - split xml into multiple filesShell 脚本 - 将 xml 拆分为多个文件
【发布时间】:2017-03-07 06:36:45
【问题描述】:

我正在尝试将一个大的 xml 文件拆分为多个文件,并在 AWK 脚本中使用了以下代码。

/<fileItem>/ {
        rfile="fileItem" count ".xml"
        print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" > rfile
        print $0 > rfile
        getline
        while ($0 !~ "<\/fileItem>" ) {
                print > rfile
                getline
        }
        print $0 > rfile
        close(rfile)
        count++
}

上面的代码生成一个xml文件列表,其名称为“fileItem_1”、“fileItem_2”、“fileItem3”等。

但是,我希望文件名类似于“item_XXXXX”,其中 XXXXX 是 XML 中的一个节点 - 如下所示

<fileItem>
<id>12345</id>
<name>XXXXX</name>
</fileItem>

所以,基本上我希望“id”节点是文件名。 谁能帮我解决这个问题?

【问题讨论】:

  • 听起来您应该为此使用适当的 XML 工具。如果您的输入是完全有规律的,那么使用 Awk 一次将一条记录读入内存并在您到达其结束标记(或文件结尾,尽管这违反 XML)时将其刷新到磁盘可能会让您做您想做的事。简而言之,当你看到开始标签时将一个变量设置为 1,然后当变量为真时,将行累加到另一个变量中;最后,当您看到结束标记时,写出累积的行并将变量设置回 0。这是一种非常标准的 Awk 技术,因此示例应该不难找到
  • 如果您曾经考虑在脚本中使用getline,请确保您首先阅读并完全理解awk.freeshell.org/AllAboutGetline,这样您就知道自己在做什么了。
  • 对于未来的读者,一个通用的解决方案比awk 命令复杂得多....为此使用 XML 工具,带有 XML 库的高级语言,带有结果的 XSLT V2.0 -文件等

标签: xml linux shell unix awk


【解决方案1】:

我不会使用getline。 (我什至在一本 AWK 书中读到不建议使用它。)我认为,使用全局变量来表示状态更简单。 (带有全局变量的表达式也可以用在模式中。)

脚本可能如下所示:

test-split-xml.awk:

/<fileItem>/ {
  collect = 1 ; buffer = "" ; file = "fileItem_"count".xml"
  ++count
}

collect > 0 {
  if (buffer != "") buffer = buffer"\n"
  buffer = buffer $0
}

collect > 0 && /<name>.+<\/name>/ {
  # cut "...<name>"
  i = index($0, "<name>") ; file = substr($0, i + 6)
  # cut "</name>..."
  i = index(file, "</name>") ; file = substr(file, 1, i - 1)
  file = file".xml"
}

/<\/fileItem>/ {
  collect = 0;
  print file
  print "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" >file
  print buffer >file
}

我为一个小测试准备了一些样本数据:

test-split-xml.xml:

<?xml version=\"1.0\" encoding=\"UTF-8\"?>
<top>
  <some>
    <fileItem>
      <id>1</id>
      <name>X1</name>
    </fileItem>
  </some>
  <fileItem>
    <id>2</id>
    <name>X2</name>
  </fileItem>
  <fileItem>
    <id>2</id>
    <!--name>X2</name-->
  </fileItem>
  <any> other input </any>
</top>

...并得到以下输出:

$ awk -f test-split-xml.awk test-split-xml.xml
X1.xml
X2.xml
fileItem_2.xml

$ more X1.xml 
<?xml version="1.0" encoding="UTF-8"?>
    <fileItem>
      <id>1</id>
      <name>X1</name>
    </fileItem>

$ more X2.xml
<?xml version="1.0" encoding="UTF-8"?>
  <fileItem>
    <id>2</id>
    <name>X2</name>
  </fileItem>

$ more fileItem_2.xml 
<?xml version="1.0" encoding="UTF-8"?>
  <fileItem>
    <id>2</id>
    <!--name>X2</name-->
  </fileItem>

$

tripleee 的评论是有道理的。因此,此类处理应仅限于个人使用,因为 XML 文件的不同(和合法)格式可能会导致此脚本处理出现错误。

您会注意到,整个脚本中没有next。这是故意的。

【讨论】:

    【解决方案2】:

    首先,您需要一个解析器。

    XML 是一种上下文数据格式。正则表达式不是。所以你可以永远使正则表达式基础处理系统真正正常工作。

    我是just bad news

    但是解析器确实存在,而且它们很容易使用。我可以用更好的数据输入给你一个更好的例子。但我会使用XML::Twigperl 来做到这一点:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    use XML::Twig;
    
    
    #subroutine to extract and process the item
    sub save_item {
       my ( $twig, $item ) = @_;
       #retrieve the id
       my $id = $item -> first_child_text('id'); 
       print "Got ID of $id\n";
    
       #create a new XML document for output. 
       my $new_xml = XML::Twig -> new;
       $new_xml -> set_root (XML::Twig::Elt -> new ( 'root' ));
    
       #cut and paste the item from the 'old' doc into the 'new'  
       #note - "cut" applies to in memory, 
       #not the 'on disk' copy. 
       $item -> cut;
       $item -> paste ( $new_xml -> root );
    
       #set XML params (not strictly needed but good style)
       $new_xml -> set_encoding ('utf-8');
       $new_xml -> set_xml_version ('1.0');
    
       #set output formatting
       $new_xml -> set_pretty_print('indented_a');
    
       print "Generated new XML:\n";
       $new_xml -> print;
    
       #open a file for output
       open ( my $output, '>', "item_$id.xml" ) or warn $!;
       print {$output} $new_xml->sprint;
       close ( $output ); 
    }
    
    #create a parser. 
    my $twig = XML::Twig -> new ( twig_handlers => { 'fileItem' => \&save_item } );
    #run this parser on the __DATA__ filehandle below.
    #you probably want parsefile('some_file.xml') instead. 
       $twig -> parse ( \*DATA );
    
    
    __DATA__
    <xml>
    <fileItem>
    <id>12345</id>
    <name>XXXXX</name>
    </fileItem>
    </xml>
    

    XML::Twig 附带 xml_split,它可能适合您的需求

    【讨论】:

      【解决方案3】:

      如果您的 XML 格式确实如此良好且一致,那么您只需要:

      awk -F'[<>]' '
      /<fileItem>/ { header="<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ORS $0; next }
      /<id> { close(out); out="item_" $3; $0=header ORS $0 }
      { print > out }
      ' file
      

      上述内容当然未经测试,因为您没有提供示例输入/输出供我们测试可能的解决方案。

      【讨论】:

        猜你喜欢
        • 2011-09-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多