136

How can I list installed packages by installation date?

I need to do this on debian/ubuntu. Answers for other distributions would be nice as well.

I installed a lot of stuff to compile a certain piece of code, and I want to get a list of the packages that I had to install.

3

12 Answers 12

92

RPM-based distributions like Red Hat are easy:

rpm -qa --last

On Debian and other dpkg-based distributions, your specific problem is easy too:

grep install /var/log/dpkg.log

Unless the log file has been rotated, in which case you should try:

grep install /var/log/dpkg.log /var/log/dpkg.log.1

In general, dpkg and apt don't seem to track the installation date, going by the lack of any such field in the dpkg-query man page.

And eventually old /var/log/dpkg.log.* files will be deleted by log rotation, so that way isn't guaranteed to give you the entire history of your system.

One suggestion that appears a few times (e.g. this thread) is to look at the /var/lib/dpkg/info directory. The files there suggest you might try something like:

ls -t /var/lib/dpkg/info/*.list | sed -e 's/\.list$//' | head -n 50

To answer your question about selections, here's a first pass.

build list of packages by dates

$ find /var/lib/dpkg/info -name "*.list" -exec stat -c $'%n\t%y' {} \; | \
    sed -e 's,/var/lib/dpkg/info/,,' -e 's,\.list\t,\t,' | \
    sort > ~/dpkglist.dates

build list of installed packages

$ dpkg --get-selections | sed -ne '/\tinstall$/{s/[[:space:]].*//;p}' | \
    sort > ~/dpkglist.selections

join the 2 lists

$ join -1 1 -2 1 -t $'\t' ~/dpkglist.selections ~/dpkglist.dates \
    > ~/dpkglist.selectiondates

For some reason it's not printing very many differences for me, so there might be a bug or an invalid assumption about what --get-selections means.

You can obviously limit the packages either by using find . -mtime -<days> or head -n <lines>, and change the output format as you like, e.g.

$ find /var/lib/dpkg/info -name "*.list" -mtime -4 | \
    sed -e 's,/var/lib/dpkg/info/,,' -e 's,\.list$,,' | \
    sort > ~/dpkglist.recent

$ join -1 1 -2 1 -t $'\t' ~/dpkglist.selections ~/dpkglist.recent \
    > ~/dpkglist.recentselections

to list only the selections that were installed (changed?) in the past 4 days.

You could probably also remove the sort commands after verifying the sort order used by dpkg --get-selections and make the find command more efficient.

6
  • 8
    I usually like apt-get more than rpm, but now debian gets -1 for not saving the installation date in the database. The debian trick includes all the installed packages, not just the selected packages, but it's a good start. May 4, 2011 at 6:53
  • 1
    For Debian you get less cruft (removes half-installed entries) if u do: grep install\ /var/log/dpkg.log
    – Pierz
    Nov 22, 2016 at 16:13
  • @Mikel - Great answer. I expanded upon the 'gather /var/lib/dpkg/info/*.list file info' and added code to filter out all but the "top level packages" (atp packages upon which no other atp packages depend). That <askubuntu.com/a/948532/723997>post answers the question "How can I view the history of apt-get install commands that I have manually executed?". Aug 22, 2017 at 5:26
  • 2
    Debian/Ubuntu: grep " install " /var/log/dpkg.log lists only the “install” lines rather than showing the “status” ones as well.
    – dessert
    Dec 20, 2017 at 19:54
  • 1
    If neither apt nor dpkg store install/modified datetime, that seems pretty unacceptable to me in 2019. We have rely on grepping log files which may or not be still on the machine? How is this the case? Feb 14, 2019 at 18:33
29

Mikel has shown how to do this at the dpkg level. In particular, /var/lib/dpkg/info/$packagename.list is created when the package is installed (and not modified afterwards).

If you used the APT tools (which you presumably did since you're concerned about automatically vs manually installed packages), there's a history in /var/log/apt/history.log. As long as it hasn't rotated away, it keeps track of all APT installations, upgrades and removals, with an annotation for packages marked as automatically installed. This is a fairly recent feature, introduced in APT 0.7.26, so in Debian it appeared in squeeze. In Ubuntu, 10.04 has history.log but the automatically-installed annotation is not present until 10.10.

1
  • 2
    As Mikel pointed out: "And eventually old /var/log/dpkg.log.* files will be deleted by log rotation, so that way isn't guaranteed to give you the entire history of your system.". See this <askubuntu.com/a/948532/723997> answer for how to detect the current top level packages (meaning the ones on which no other package depends) Aug 22, 2017 at 5:38
12

Here is the one-liner everyone wants and needs:

for x in $(ls -1t /var/log/dpkg.log*); do zcat -f $x |tac |grep -e " install " -e " upgrade "; done |awk -F ":a" '{print $1 " :a" $2}' |column -t

The result will show all (newly) installed and upgraded packages in chronological order.

The line explanation:

  • ls -1t - get all dpkg.log* file names in chronological order
  • zcat -f - IF file is of gzip type then decompress it, ELSE just pass on the content.
  • tac - Reverse output of cat, line-by-line to makes sure we get the correct chronological order.
  • grep - Only check for installed or upgrade packages.
  • awk -F ':a' - Separate the architecture field from the package name
  • column -t - pretty print the columns separated by space

One would of course like to make an alias for this, but unfortunately it is not possible as awk depends on both single and double quotes. In that regard this is best put into a bash script and where the : separator is handled better for other architectures in the field column.

The output is:

2018-03-06  18:09:47  upgrade  libgomp1                     :armhf  6.3.0-18+rpi1                 6.3.0-18+rpi1+deb9u1
2018-03-05  15:56:23  install  mpg123                       :armhf  <none>                        1.23.8-1
2018-03-05  15:56:23  install  libout123-0                  :armhf  <none>                        1.23.8-1
2018-01-22  17:09:45  install  libmailtools-perl            :all    <none>                        2.18-1
2018-01-22  17:09:44  install  libnet-smtp-ssl-perl         :all    <none>                        1.04-1

Drawback:

  • As shown above, it only works on ARM architecture and need slight modification for the architecture field separator
  • Need to be put into a script for easy alias
  • Has not been tested across other *nix systems
6

Rough, but works :

for fillo in `ls -tr /var/lib/dpkg/info/*.list` ; 
    do basename ${fillo} | sed 's/.list$//g' ; 
done > forens.txt

ls -ltr /var/lib/dpkg/info/*.list > forentime.txt

for lint in `cat forens.txt` ; do 
    echo -n "[ ${lint} Installed ] : " ; 
    echo -n "`grep /${lint}.list forentime.txt | awk '{ print $6, $7, $8 }'` : " ; 
    ( ( grep -A3 " ${lint}$" /var/lib/apt/extended_states | \
        grep '^Auto' > /dev/null ) && echo "Auto" ) || echo "Manual" ; 
done > pkgdatetime.txt
2
  • 2
    Boo, hiss for parsing output from ls. See mywiki.wooledge.org/ParsingLs for notes on why this is dangerous / inherently buggy -- the safer option is to use either find -printf or stat --format to generate a stream that can be unambiguously parsed. Apr 10, 2015 at 17:17
  • @CharlesDuffy Nice link, but for the purpose of simplicity, using ls -al --time-style=long-iso should be helpful. In addition, it is probably unheard of that someone would name an APT package with \n\t\r\v in its name.
    – not2qubit
    Apr 26, 2018 at 11:12
5

The /var/log/apt/history.log file has an awkward format IMHO.

Start-Date: {date} {time} Commandline: {command} {options ...} Install: {package (version)}, ..., {package (version)}, ... End-Date: {date} {time}

I would have preferred a more log-file formatted record

{date}{time}{tab}{package}{tab}{version}{tab}{command}{options}\n

or some XML showing not only a {package} but any {dependencies}.

As currently implemented, you can discover the information you seek but it requires some forensic processing to extract the details.

3

This works for me on a Debian system, I'm guessing the file format has changed since 2011. This system is pretty fresh so I wouldn't expect this to work on an older system, although that might just require unzipping the logs and using a glob to refer to all of them.

grep 'install ' /var/log/dpkg.log.1 | sort | cut -f1,2,4 -d' '

The first two fields in each line of the file /var/log/dpkg.log are the date and time. Note the trailing space with install in the grep part, this is because upgrades can trigger installs but if I understood correctly you wanted to know what was installed by users.

1
  • 1
    Exactly what I do. Easy. But you can use zgrep and your all your .gz logs will get searched like zgrep ' install' /var/log/dpkg.log*. Place the space before the word "install" to prevent those pesky "half-installs". I had to use cut -f1,5 to get the package name field. Of course eventually the old logs rotate out.
    – geoO
    Apr 24, 2017 at 9:19
3

GNU/Linux Debian has no built-in tools for this problem, but all information about programs installed in the standard way is saved in files with program-name.list in the location /var/lib/dpkg/info/. But there is no information about manually installed programs there.


A long single-line solution:

for file_list in `ls -rt /var/lib/dpkg/info/*.list`; do \
    stat_result=$(stat --format=%y "$file_list"); \
    printf "%-50s %s\n" $(basename $file_list .list) "$stat_result"; \
done

Explanation:

  1. ls -rt outputs files sorted by date modification in the reverse order, i.e. with the newest files at the end of the list.
  2. stat prints the file's date in human readable form.
  3. printf displays the package name and the date of its last modification.
  4. The for loop as a whole prints package names and dates from oldest to newest.

Output example (truncated):

.........................................
gnome-system-log                            2016-09-17 16:31:58.000000000 +0300
libyelp0                                    2016-09-17 16:32:00.000000000 +0300
gnome-system-monitor                        2016-09-17 16:32:00.000000000 +0300
yelp-xsl                                    2016-09-17 16:32:01.000000000 +0300
yelp                                        2016-09-17 16:32:03.000000000 +0300
gnome-user-guide                            2016-09-17 16:32:18.000000000 +0300
libapache2-mod-dnssd                        2016-09-17 16:32:19.000000000 +0300
.........................................
linux-compiler-gcc-4.8-x86                  2017-02-26 20:11:02.800756429 +0200
linux-headers-3.16.0-4-amd64                2017-02-26 20:11:10.463446327 +0200
linux-headers-3.16.0-4-common               2017-02-26 20:11:17.414555037 +0200
linux-libc-dev:amd64                        2017-02-26 20:11:21.126184016 +0200
openssl                                     2017-02-26 20:11:22.094098618 +0200
unzip                                       2017-02-26 20:11:23.118013331 +0200
wireless-regdb                              2017-02-26 20:11:23.929949143 +0200
nodejs                                      2017-02-26 20:11:33.321424052 +0200
nasm                                        2017-02-28 16:41:17.013509727 +0200
librecode0:amd64                            2017-03-01 10:38:49.817962640 +0200
libuchardet0                                2017-03-01 10:41:10.860098788 +0200
tree                                        2017-03-04 14:32:12.251787763 +0200
libtar0                                     2017-03-07 09:51:46.609746789 +0200
libtar-dev                                  2017-03-07 09:51:47.129753987 +0200

The main defect of this solution.is that it's not well-tested in production.

1
  • This is a beautiful solution that get the job almost done. It's only drawbacks, are that (1) it's very slow and (2) that it only show when a package was last updated, not any of it's previous versions. This, of course is not a problem of the one-liner, but how dpkg doesn't keep track of history in /var/lib/dpkg/info/. That is also why using /var/log/dpkg.log* may be preferred.
    – not2qubit
    Apr 26, 2018 at 11:49
1

Noting this because you mention that other distribution answers are welcome. rpm has a large set of output format tags, one of which is INSTALLTIME. (Using wget as an example)

rpm -qi wget --qf "%{NAME},%{INSTALLTIME}\n" | tail -n 1
wget,1454014156

This can be formatted in a few ways. I use it in this way:

rpm -qi wget --qf "%{NAME},%{INSTALLTIME:date}\n" | tail -n 1
wget,Thu 28 Jan 2016 03:49:16 PM EST

These two pages have a ton of great info on solving RPM metadata issues:

http://www.rpm.org/max-rpm/s1-rpm-query-parts.html

http://www.rpm.org/max-rpm/s1-rpm-query-handy-queries.html

Sorting this information would give you a working solution for your issue.

1

It is rough, but works as quickly as other solutions. Date format is yyyymmddhhmmss, meaning that a bit or reordering and format removal results in a number that can be sorted.

Many thanks to the other solutions, this list package names in order of installation that could be used in a built to make copy operating system.

find /var/lib/dpkg/info -name "*.list" -exec stat -c $'%n\t%y' {} \; \
| sed -e 's,/var/lib/dpkg/info/,,' -e 's,\.list\t,\t,' \
| sort | awk '{print $2$3" "$1}' | sed '0,/RE/s/-//' \
| sed '0,/RE/s/-//' | sed '0,/RE/s/://' | sed '0,/RE/s/://' \
| sed '0,/RE/s/\\.//' | sed 's/:armhf//' | sort | awk '{print $2}'
1
  • Welcome @alexander-cave! Please add a few lines of output so that people can see what kind of output to expect.
    – not2qubit
    Sep 5, 2018 at 19:50
0

With rotated logfiles in apt you could:

zcat -f /var/log/dpkg.log* | grep " install " | less
0

Ive achieved it on Kubuntu with the following commands:

  1. List the packages.
  2. read every standard input.
  3. store the full path (path | created date) with the format "%n|%y" into a variable.
  4. store the name of the file into a variable
  5. if both previous commands were executed successfully, values will be printed in a single line.
ls /var/lib/dpkg/info/*.list |while read xfile; do wpath=$(stat -c '%n|%y' ${xfile}) && wfile=$(basename ${xfile}) && printf "${wfile}|${wpath}\n" ;done

Best regards,

0

Put the below in your shell rc file.

# list date and name of installation packages
function lspkg_func {
    # Save the installation text to a variable.
    local installation_text=$(grep " install " /var/log/dpkg.log*)
    for i in $(dpkg-query -f='${Package}\n' -W); do
        local pkg_date=$(echo "$installation_text" | grep " install $i:" | sort -n | tail -n1 | cut -d' ' -f 1,2)
        echo "${pkg_date:-1971-01-01 00:00:00}\t$i"         # if pkg_date is null, use `1970-01-01 00:00:00` instead.
    done
}

# sort the result of `lspkg_func` by date descendingly
alias lspkgs='lspkg_func | sort -nr | less -i'

The output of lspkgs command.

2022-09-09 20:49:38     aptitude
2022-09-09 20:49:36     libxapian30
2022-09-09 20:49:31     libcwidget4
2022-09-09 20:49:29     libsigc++-2.0-0v5
2022-09-09 20:49:26     libboost-iostreams1.74.0
2022-09-09 20:49:16     aptitude-common
2022-09-09 17:35:17     nodejs-doc
2022-09-09 17:35:12     nodejs
2
  • 1
    Please don’t post code-only answers; explain what you’re doing.   And please don’t post images of text; post text as text. Sep 11, 2022 at 4:53
  • Thank you. I have perfected my answer. Sep 16, 2022 at 4:46

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .