Getting a list of outdated versions with Python I have a list of boxes and need to list only the outdated ones.

For example, input will look something like this:


maier/alpine-3.1.3-x86_64 (virtualbox, 1.2.0)
maier/alpine-3.1.3-x86_64 (virtualbox, 1.3.0)
ubuntu/precise64          (virtualbox, 20151202.0.0)
ubuntu/vivid64            (virtualbox, 20150821.0.0)
ubuntu/vivid64            (virtualbox, 20150903.0.0)
ubuntu/vivid64            (virtualbox, 20151113.0.0)
ubuntu/vivid64            (virtualbox, 20151127.0.0)
ubuntu/vivid64            (virtualbox, 20151211.0.0)
ubuntu/wily64             (virtualbox, 20151203.0.0)



Output will look roughly like this:


maier/alpine-3.1.3-x86_64 (virtualbox, 1.2.0)
ubuntu/vivid64            (virtualbox, 20150821.0.0)
ubuntu/vivid64            (virtualbox, 20150903.0.0)
ubuntu/vivid64            (virtualbox, 20151113.0.0)
ubuntu/vivid64            (virtualbox, 20151127.0.0)



Note the format consists of: name, provider, version

It seems like there would be an easy way to do this from Bash scripting alone, e.g. I can version sort a particular column with sort, but I wasn't quite able to figure it out so I roughed it out in Python instead.

Is there are better way to do this?

#!/usr/bin/python3
# -*- coding: utf-8 -*-

"""Attempt to remove outdated Vagrant boxes"""

import os
import re
import subprocess

from distutils.version import StrictVersion


def search(text):
    """Return a dictionary containing all the named subgroups of the match."""

    patt = r'^(?P&lt;name&gt;[^ ]+) +\((?P&lt;provider&gt;[^ ]+), +(?P&lt;version&gt;[^ ]+)\)$'
    return re.search(patt, text).groupdict()


output = subprocess.check_output(['vagrant', 'box', 'list'],
                                 stderr=subprocess.DEVNULL).decode().rstrip()

box_list = (search(line) for line in output.splitlines())

box_dict = {}

for box in box_list:
    name = box.get('name')
    version = box.get('version')
    box_dict[name] = box_dict.get(name, []) + [version]

outdated_boxes = {}

for box in box_dict:
    versions = box_dict[box]
    # remove the most recent version
    if len(versions) &gt; 1:
        outdated_boxes[box] = sorted(versions, key=StrictVersion)[:-1]

for box in outdated_boxes:
    versions = outdated_boxes[box]
    for version in versions:
        command = ['vagrant', 'box', 'remove', box, '--box-version', version]
        print(*command)
        os.system(' '.join(command))
        # process = subprocess.Popen(command)
        # process.wait()
        print()
Since everything is printed in order, the "obsolete" ones are all but the last version for the first word. That's tricky. But if we looked at the text backwards, we're printing all but the first one. 

And that's easy with awk:

awk '!($1 in seen){print }{seen[$1] = 1}'


And then just invert the input twice:

run_your_version_cmd_here | tac | awk '$1 not in seen{print }{seen[$1] = 1}' | tac