Ubuntu – How to get a list of newly installed packages after time x without removed packages after time x

aptdpkgpackage-management

From /var/log/apt/history.log it is possible to retrieve a list of installed and removed packages within time x based on timestamps, but editing the file is rather annoying, so I'm looking for a (set of) command(s) to get the effective list of packages which have been installed after timestamp x_0(= packages which have been installed after timestamp x_0 minus packages which have been removed after timestamp x_0) in form of a list of package names.

software-center only displays changes chronologically and synaptic doesn't have a column which represents installation time. dpkg-query looks promising, but I'd appreciate some help from someone who can figure this out in minutes rather than days (the latter applies to myself).

I'm running Ubuntu 14.10.

Best Answer

It seems that dpkg doesn't show explicitly any information about installation dates of packages.

So, for a single run I would use something like the following one-liner:

cat /var/log/apt/history.log |perl -lne 'next unless /^Start-Date: ([^ ]+)/ && $1 ge "2015-04-26" .. 1; next unless /^(Install|Purge|Remove): (.*)$/; $c = $1; $s = $2; $s =~ s/\([^\)]*\)//g; @packages = map { /^(.*?):/ ? $1 : $_} split /\s*,\s*/, $s; if ($c=~/^Install$/){ $h{$_} = 1 for @packages;} if ($c=~/^(Purge|Remove)$/) {delete $h{$_} for @packages;} END{print for sort keys %h;}'

Start date (x_0) has been hard-coded in the command ("2015-04-26").

For time to time usage more suitable would be a stand-alone script, like this installed_packages.pl:

#!/usr/bin/perl

# use as follows:
# ./installed_packages.pl '2015-04-26' /var/log/apt/history.log
# or
# ./installed_packages.pl '2015-04-26 16:08:36' /var/log/apt/history.log

use strict;
use warnings;

# the script expects start date as first argument
my $START_DATE = shift @ARGV;

# hash (dict) to accumulate installed & not removed packages 
my %installed;
# flag to mark the beginning of "interesting" period of time
my $start = 0;

# loop over lines from the input file
while (my $line = <>){
    # remove end-of-line character 
    chomp $line;

    # skip everything until date becomes greater (or equal) than our x_0
    $start ||= $line =~ /^Start-Date: ([^ ]+)/ && $1 ge $START_DATE;
    next unless $start;

    # we're only interested in lines like 
    # Install: tupi-data:amd64 (0.2+git02-3build1, automatic), tupi:amd64 (0.2+git02-3build1), libquazip0:amd64 (0.6.2-0ubuntu1, automatic)
    # or
    # Remove: aptitude-common:amd64 (0.6.8.2-1ubuntu4)
    # + separate action (install/remove/purge) from package list
    next unless $line =~ /^(Install|Purge|Remove): (.*)$/; 
    my ($action, $packages_str) = ($1, $2);

    # remove versions from the list (they're in parentheses)
    $packages_str =~ s/\(.*?\)//g;

    # split single line into array of package names
    my @packages = split /\s*,\s*/, $packages_str;
    # remove architecture name (comes after ':')
    s/:.*// for @packages;

    # if packages have been installed, add them all to the hash (dict) of installed packages
    if ($action =~ /^Install$/ ){
        $installed{$_} = 1 for @packages;
    }

    # if packages have been removed, remove them all from the hash (dict) of installed packages
    if ($action =~ /^(Purge|Remove)$/) {
        delete $installed{$_} for @packages;
    }
}

# print all installed and not removed packages, in alphabetical order
for my $p ( sort keys %installed ){
    print "$p\n";
}

Usage:

./installed_packages.pl '2015-04-26' /var/log/apt/history.log

or

perl ./installed_packages.pl '2015-04-26' /var/log/apt/history.log

For frequent interactive use I'd add validation of script arguments (format of start date), implement -h switch to display short help, and possibly convert start date to named switch (--start).

Good luck!