dpkg-query --showformat='${db:Status-Status}'
This produces a small output string which is unlikely to change and is easy to compare deterministically without grep:
pkg=hello
status="$(dpkg-query -W --showformat='${db:Status-Status}' "$pkg" 2>&1)"
if [ ! $? = 0 ] || [ ! "$status" = installed ]; then
sudo apt install $pkg
fi
The $? = 0 check is needed because if you've never installed a package before, and after you remove certain packages such as hello, dpkg-query exits with status 1 and outputs to stderr:
dpkg-query: no packages found matching hello
instead of outputting not-installed. The 2>&1 captures that error message too when it comes preventing it from going to the terminal.
For multiple packages:
pkgs='hello certbot'
install=false
for pkg in $pkgs; do
status="$(dpkg-query -W --showformat='${db:Status-Status}' "$pkg" 2>&1)"
if [ ! $? = 0 ] || [ ! "$status" = installed ]; then
install=true
break
fi
done
if "$install"; then
sudo apt install $pkgs
fi
The possible statuses are documented in man dpkg-query as:
n = Not-installed
c = Config-files
H = Half-installed
U = Unpacked
F = Half-configured
W = Triggers-awaiting
t = Triggers-pending
i = Installed
The single letter versions are obtainable with db:Status-Abbrev, but they come together with the action and error status, so you get 3 characters and would need to cut it.
So I think it is reliable enough to rely on the uncapitalized statuses (Config-files vs config-files) not changing instead.
dpkg -s exit status
This unfortunately doesn't do what most users want:
pkgs='qemu-user pandoc'
if ! dpkg -s $pkgs >/dev/null 2>&1; then
sudo apt-get install $pkgs
fi
because for some packages, e.g. certbot, doing:
sudo apt install certbot
sudo apt remove certbot
leaves certbot in state config-files, which means that config files were left in the machine. And in that state, dpkg -s still returns 0, because the package metadata is still kept around so that those config files can be handled more nicely.
To actually make dpkg -s return 1 as desired, --purge would be needed:
sudo apt remove --purge certbot
which actually moves it into not-installed/dpkg-query: no packages found matching.
Note that only certain packages leave config files behind. A simpler package like hello goes directly from installed to not-installed without --purge.
Tested on Ubuntu 20.10.
Python apt package
There is a pre-installed Python 3 package called apt in Ubuntu 18.04 which exposes an Python apt interface!
A script that checks if a package is installed and installs it if not can be seen at: https://stackoverflow.com/questions/17537390/how-to-install-a-package-using-the-python-apt-api/17538002#17538002
Here is a copy for reference:
#!/usr/bin/env python
# aptinstall.py
import apt
import sys
pkg_name = "libjs-yui-doc"
cache = apt.cache.Cache()
cache.update()
cache.open()
pkg = cache[pkg_name]
if pkg.is_installed:
print "{pkg_name} already installed".format(pkg_name=pkg_name)
else:
pkg.mark_install()
try:
cache.commit()
except Exception, arg:
print >> sys.stderr, "Sorry, package installation failed [{err}]".format(err=str(arg))
Check if an executable is in PATH instead
See: https://stackoverflow.com/questions/592620/how-can-i-check-if-a-program-exists-from-a-bash-script/22589429#22589429
See also