从简单的XML文件中提取数据

47

我有一个包含以下内容的XML文件:

<?xml version="1.0" encoding="utf-8"?>
<job xmlns="http://www.sample.com/">programming</job>
我需要一种方法来提取 <job..></job> 标签中的内容,在这种情况下是 programmin。这应该在 Linux 命令提示符下使用 grep/sed/awk 完成。

如果您的XML文件包含以下内容:<?xml version="1.0" encoding="utf-8"?> <job xmlns="http://www.sample.com/">Tom & Jerry</job>您希望结果保留XML转义符号:Tom & Jerry还是希望转义符号被还原,就像XML解析器一样:Tom & Jerry如果是后者,很抱歉,我不知道如何使用Unix文本工具实现。 - Paul Clapham
@Paul s/&amp;/\&/g,同样适用于&quot;等,当然这不适用于用户定义的实体等。 - 13ren
https://dev59.com/5WQm5IYBdhLWcg3wyhfk#17333829 - Stack Underflow
11个回答

68

你真的必须只使用那些工具吗? 它们不是为XML处理而设计的,虽然可能会获得正常运行的东西,但在极限情况下(如编码、换行符等)会失败。

我建议使用xml_grep:

xml_grep 'job' jobs.xml --text_only

这将输出:

programming

在Ubuntu/Debian上,xml_grep位于xml-twig-tools软件包中。


为xml_grep提供详细的安装说明将非常有帮助。 - paul_h
6
使用"sudo apt-get install xml-twig-tools"安装xml-twig-tools软件包。 - FredFury
1
“grep”只是无痛文本搜索的同义词。 - dr0i

16
 grep '<job' file_name | cut -f2 -d">"|cut -f1 -d"<"

1
只有当标签位于单独的行上时,它才会失败。 - ghostdog74
8
有大约十几种其他的方式,能够使格式良好的XML导致失败。 - Robert Rossney

12

使用xmlstarlet:

echo '<job xmlns="http://www.sample.com/">programming</job>' | \
   xmlstarlet sel -N var="http://www.sample.com/" -t -m "//var:job" -v '.'

4
有许多不同的工具使用标准的XPath符号从XML中提取信息,xmlstarlet只是其中之一。其他工具包括xmllintxpath等。请参考https://dev59.com/FmUp5IYBdhLWcg3wLVOn。 - tripleee

9
请不要在XML上使用基于行和正则表达式的解析。这是一个坏主意。您可以拥有具有不同格式的语义相同的XML,而正则表达式和基于行的解析无法处理它。
像一元标记和可变行包装之类的东西 - 这些片段“说”相同的事情:
<root>
  <sometag val1="fish" val2="carrot" val3="narf"></sometag>
</root>


<root>
  <sometag
      val1="fish"
      val2="carrot"
      val3="narf"></sometag>
</root>

<root
><sometag
val1="fish"
val2="carrot"
val3="narf"
></sometag></root>

<root><sometag val1="fish" val2="carrot" val3="narf"/></root>

希望这能清楚地解释为什么制作基于正则表达式/行的解析器很困难?幸运的是,你不需要这样做。许多脚本语言都有至少一个解析器选项,有时甚至有更多选项。
正如之前的发帖者所暗示的 - 可以使用xml_grep。实际上,这是一个基于perl库XML::Twig 的工具。然而,它使用“xpath表达式”来查找内容,并区分文档结构、属性和“内容”。

E.g.:

xml_grep 'job' jobs.xml --text_only

然而,为了提供更好的答案,以下是一些基于您的源数据自行创建的示例:

第一种方法:

使用 twig 处理程序 捕获特定类型的元素并对其进行操作。这种方式的优点是它在解析 XML 时“随时随地”进行,如果需要,可以在运行时修改它。当您处理大文件并使用 purgeflush 时,这对于丢弃“已处理”的 XML 特别有用:

#!/usr/bin/perl

use strict;
use warnings;

use XML::Twig;

XML::Twig->new(
    twig_handlers => {
        'job' => sub { print $_ ->text }
    }
    )->parse( <> );

这段内容是关于编程的,它将使用<>来获取输入(通过管道或命令行指定./myscript somefile.xml),并处理每个job元素,提取和打印相关文本。您可能需要使用print $_ -> text,"\n"来插入换行符。由于它匹配了“job”元素,因此也会匹配嵌套的工作元素。
<job>programming
    <job>anotherjob</job>
</job>

这段代码会匹配两次,但是输出的一部分也会重复。如果您喜欢,也可以使用 /job 进行匹配。这样可以方便地打印和删除元素,或者复制并粘贴一个修改过的 XML 结构。

另一种方法是先解析,然后根据结构“打印”:

my $twig = XML::Twig->new( )->parse( <> );
print $twig -> root -> text;

作为根元素,job 只需要打印其文本内容即可。
但我们可以更加精确地查找 job/job 并专门打印它们的内容。
my $twig = XML::Twig->new( )->parse( <> );
print $twig -> findnodes('/job',0)->text;

你可以使用XML::Twig的pretty_print选项来重新格式化你的XML文件:
XML::Twig->new( 'pretty_print' => 'indented_a' )->parse( <> ) -> print;

有各种输出格式选项,但对于简单的XML(如您的),大多数选项看起来相似。


8
只需使用awk,无需其他外部工具。如果您要查找的标签出现在多行中,则可以使用以下方法。
$ cat file
test
<job xmlns="http://www.sample.com/">programming</job>
<job xmlns="http://www.sample.com/">
programming</job>

$ awk -vRS="</job>" '{gsub(/.*<job.*>/,"");print}' file
programming

programming

</job> 是有效的,但是你的脚本无法识别它。<!-- </job> --> 是需要被忽略的注释(而 <!CDATA[[ </job> ]]> 则是字面数据),但是你的脚本不知道这些。还有一些情况,比如有一个 DTD 定义了新的宏,使得 &foo; 扩展为本地指定的内容,以及简单的情况,比如需要将 &amp; 转换为 &。试图自己编写 XML 解析(或更糟糕的是生成)会导致无数的边角情况和需要逐个修复的细节问题。 - Charles Duffy

6
使用sed命令:
示例:
$ cat file.xml
<note>
        <to>Tove</to>
                <from>Jani</from>
                <heading>Reminder</heading>
        <body>Don't forget me this weekend!</body>
</note>

$ cat file.xml | sed -ne '/<heading>/s#\s*<[^>]*>\s*##gp'
Reminder

解释:

cat file.xml | sed -ne '/<pattern_to_find>/s#\s*<[^>]*>\s*##gp'

n - 抑制打印所有行
e - 脚本

/<pattern_to_find>/ - 查找包含指定模式的行,例如<heading>

接下来是替换部分 s///p,它删除除所需值外的所有内容,其中将/替换为#以提高可读性:

s#\s*<[^>]*>\s*##gp
\s* - 包括存在的空格(结尾相同)
<[^>]*> 表示<xml_tag>作为非贪婪regex替代方案,因为<.*?>在sed中不起作用
g - 替换所有内容,例如关闭xml</xml_tag>标记


5
假设在同一行,从标准输入获取输入:
sed -ne '/<\/job>/ { s/<[^>]*>\(.*\)<\/job>/\1/; p }'

注意: -n 停止自动输出所有内容;-e 表示它是一个一行命令(而不是脚本);/<\/job> 的作用类似于 grep 命令;s 剥离开标签和属性以及结束标签;; 是一个新语句的开始;p 打印;{} 使 grep 命令应用于两个语句,作为一个整体。


0
有点晚了。 xmlcutty 可以从 XML 中剪切节点:
$ cat file.xml
<?xml version="1.0" encoding="utf-8"?>
<job xmlns="http://www.sample.com/">programming</job>
<job xmlns="http://www.sample.com/">designing</job>
<job xmlns="http://www.sample.com/">managing</job>
<job xmlns="http://www.sample.com/">teaching</job>

path参数指定要剪切的元素路径。在这种情况下,由于我们不关心标签,因此将标签重命名为\n,以便我们得到一个漂亮的列表:

$ xmlcutty -path /job -rename '\n' file.xml
programming
designing
managing
teaching

请注意,XML 一开始就不是有效的(没有根元素)。xmlcutty 也可以处理略有问题的 XML。

0

这样怎么样:

cat a.xml | grep '<job' | cut -d '>' -f 2 | cut -d '<' -f 1

4
UUOC是"Useless Use of Cat"的缩写,意思是无用地使用了cat命令。在上述命令中,可以直接使用grep命令来搜索a.xml文件中包含"<job"的行,而无需使用cat命令将其输出并通过管道传递给grep命令。因此,建议改为:grep '<job' a.xml | ... - ghostdog74
@ghost 但是但是但是,我认为这样做更加清晰/美观/不会浪费太多资源/我有权利浪费进程! http://partmaps.org/era/unix/award.html#cat (实际上,我认为编辑文件名更容易,因为更靠近开头) - 13ren
3
如果你使用 < a.xml | grep ...,那么你会更接近开头。 - Thor

0

yourxmlfile.xml

<item> 
  <title>15:54:57 - George:</title>
  <description>Diane DeConn? You saw Diane DeConn!</description> 
</item> 
<item> 
  <title>15:55:17 - Jerry:</title> 
  <description>Something huh?</description>
</item>

在你的xml文件中使用grep 'title'

  <title>15:54:57 - George:</title>
  <title>15:55:17 - Jerry:</title>

在你的xml文件中使用grep 'title'命令 | awk -F">" '{print $2}'

  15:54:57 - George:</title
  15:55:17 - Jerry:</title

grep 'title' yourxmlfile.xml | awk -F">" '{print $2}' | awk -F"<" '{print $1}'

在编程中,上述代码是用于从XML文件中提取标题的命令。它使用grep命令查找包含“title”的行,然后使用awk命令分割字符串并提取所需的文本。

  15:54:57 - George:
  15:55:17 - Jerry:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接