Extract multiple attributes values in XML tags I have a XML file (test.xml) that can be sum up as follow (I filtered it so it will be more readable):

&lt;coverage complexity="0" line-rate="0.66" lines-covered="66" lines-valid="100"&gt;
    &lt;packages&gt;
        &lt;package complexity="0" line-rate="0.66" name="."&gt;
            &lt;classes&gt;
                &lt;class complexity="0" name="file_a.py" line-rate="0.7674"&gt;
                &lt;class complexity="0" name="file_b.py" line-rate="0.2727"&gt;
                &lt;class complexity="0" name="file_c.py" line-rate="1"&gt;
            &lt;/classes&gt;
        &lt;/package&gt;
    &lt;/packages&gt;
&lt;/coverage&gt;


For each line, I want to extract both name and line-rate info, for example output could be:

. 0.66
file_a.py 0.7674
file_b.py 0.2727
file_c.py 1


Note that I'll like to skip the 1st line since it as no name field.

Right now I managed to get that output with the following bash script:

#!/bin/bash

# Extract info in lines containing either "&lt;package " or "&lt;class "
linerates=`grep '&lt;package \|&lt;class ' test.xml | awk -F "line-rate=" '{print $2}' | awk -F '"' '{$
names=`grep '&lt;package \|&lt;class ' test.xml | awk -F "name=" '{print $2}' | awk -F '"' '{print $2}$

# Transform to array
linerates=(${linerates// / })
names=(${names// / })

# Print table
for i in "${!names[@]}"
do
        echo ${names[$i]} ${linerates[i]}
done


Since the code is quite ugly, I wonder if there is a way to extract those two informations in a more elegant way, let say in one command line / without the need to use a for loop



Edit

I switch to python and got this:

from bs4 import BeautifulSoup as bs

with open('test.xml', 'r') as file:
    content = file.readlines()
    content = "".join(content)
    bs_content = bs(content, 'lxml')

list_ = list(bs_content.find('classes').children)
list_ = list(filter(lambda a: a != '\n', list_))

for c in list_:
    print(c.get('name'), c.get('line-rate'))


The output is a bit reduced (but I'm OK with it) 

file_a.py 0.7674
file_b.py 0.2727
file_c.py 1


I am still looking to do it using a single command line but for now I will go with the python version



Edit (following greybeard's comment)


I filtered my XML file to remove all unnecessary lines (none of them have attributes name nor line-rate). E.g of removed lines:

&lt;lines&gt;
     &lt;line hits="1" number="1"/&gt;
&lt;/lines&gt;

Not much complications my file is generated so the attributes should always be in the same order. Coverage, package and class have more attributes. E.g. for "coverage" also has a timestamp and a version attributes ; "class" has a filename attribute which is the same as name


Feel free to ask if I forgot some other information
There are many issues with your posted example that prevent it from being sanely parsed by XML, including lack of closing tags and lack of a starting xml tag. You say that the content is generated: if you generated it, you should try to fix this. Anyway.

import re

pat = re.compile('name="(.*?)".*'
                 'line-rate="([0-9.]*)"')

with open('test.xml') as f:
    for line in f:
        match = pat.search(line)
        if match:
            print(match.expand(r'\1 \2'))


This makes many assumptions:


The attributes actually are in the same order every time (in your example, they aren't, despite you saying that they should be)
The file is guaranteed to have at most one line-rate/name pair per line
Those two attributes are always on the same line as each other


If all of those conditions are satisfied, the above works.

Actual XML

If (as it seems you suggested in a now-rolled-back edit) your input can actually be valid XML, then a method that will perform more validation is

from xml.sax import parse, ContentHandler

class Handler(ContentHandler):
    def startElement(self, name, attrs):
        name, rate = attrs.get('name'), attrs.get('line-rate')
        if name and rate:
            print(f'{name} {rate}')

parse('test.xml', Handler())


I do not know if it is faster, but it's probably a better idea to do this and halt-and-catch-fire if the XML is malformed, which SAX will do for you.