1

I have an XML file with the following syntax

<element>
  <id>0</id>
  <tag1>something</tag1>
  ...
  <tagn>something</tagn>
</element>
<element>
  <id>1</id>
  <tag1>something</tag1>
  ...
  <tagn>something</tagn>
</element>

What Perl one-liner would find the largest value from the 'id' elements?

I am a Perl newbie, yet, I know I can get the values of the id elements by doing:

perl -wne 'print $1 if /<id>(\d+)<\/id>/'

which produces "0123456789" (a string with just the numbers?).

Based on this answer https://unix.stackexchange.com/a/130903/374251 I could try to store the matches in a list and print the max value from the list. However, I don't know how to do so in a one-liner.

  • read in perlmod(1) about the END codeblocks: perl -nle '$m = $1 if m{<id>(\d+)</id>} and $1 > $m; END{print $m}' – mosvy Sep 25 at 22:37
  • If you have to parse xml, you can look into XML::Parser::Expat (or XML::LibXML::SAX and Reader). Do not use the default DOM interface of XML::LibXML or any other DOM tool like xmlstarlet other than for useless toys and demos (and for answering Qs on stackexchange ;-)) as those will first load the whole file into memory, and are able to bring a machine to its knees even with moderately sized xmls (smaller than 1G). – mosvy Sep 25 at 23:13
  • I have no problem at all parsing multi-megabyte XML files with just XML::LibXML and load_xml() as in my answer below. For anything up to a few tens or even hundreds of megabytes, it will be fine. xmlstarlet works fine too (but the command-line options are a PITA to remember so I prefer perl). For extremely large files, it would make sense to iterate through the elements without reading them all into RAM at once. Your panic is overblown. The sky isn't falling. – cas Sep 26 at 2:28
0

Standalone perl script:

#!/usr/bin/perl

use strict;
use XML::LibXML;
use List::Util qw(max);

my $filename = './input.xml';
my $dom = XML::LibXML->load_xml(location => $filename);

my @ids = map { $_->to_literal() } $dom->findnodes('/data/element/id');

print max(@ids), "\n";

Uglier, harder-to-understand and harder-to-edit one-liner version:

perl -MXML::LibXML -MList::Util=max -e '
  $dom = XML::LibXML->load_xml(location => shift);
  @ids = map { $_->to_literal() } $dom->findnodes("/data/element/id");
  print max(@ids), "\n";' input.xml

Note: both of the above assume that the <element>s are wrapped inside a <data> path. If not, adjust the xpath in the findnodes() function call to suit your actual data.

I ran them both with the following input.xml file:

<?xml version='1.0' encoding='UTF-8'?>
<data>
<element>
  <id>0</id>
  <tag1>something</tag1>
  <tagn>something</tagn>
</element>
<element>
  <id>1</id>
  <tag1>something</tag1>
  <tagn>something</tagn>
</element>
<element>
  <id>2</id>
  <tag1>something</tag1>
  <tagn>something</tagn>
</element>
<element>
  <id>3</id>
  <tag1>something</tag1>
  <tagn>something</tagn>
</element>
<element>
  <id>4</id>
  <tag1>something</tag1>
  <tagn>something</tagn>
</element>
<element>
  <id>5</id>
  <tag1>something</tag1>
  <tagn>something</tagn>
</element>
</data>

and both got the correct result of 5.


BTW, either version can be made to read STDIN by changing the lines with location => $filename or location => shift to:

my $dom = XML::LibXML->load_xml(IO => *STDIN);

The my is optional in the one-liner version without use strict, but is required in the stand-alone version.


BTW, it's also easy to modify either script so that both the input filename and the xpath can be specified on the command line. so that you have a generic tool for getting the max() value of any xpath element. e.g.

#!/usr/bin/perl

use strict;
use XML::LibXML;
use List::Util qw(max);

my $dom = XML::LibXML->load_xml(location => shift);
my @ids = map { $_->to_literal() } $dom->findnodes(shift);
print max(@ids), "\n";

Run as, e.g.,

$ xml-max.pl input.xml /data/element/id
5

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.