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: How to install a package using the python-apt API
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: How can I check if a program exists from a Bash script?
See also
command -v <command>
; notwhich <command>
. Also see Check if a program exists from a Bash script. – Erdrichapt install
ing an already installed package will also mark it as manually installed, a very undesired side-effect if that package was previously set as automatically installed. – Edgy