I'm working on a Ubuntu system and currently this is what I'm doing:
if ! which command > /dev/null; then
echo -e "Command not found! Install? (y/n) \c"
read
if "$REPLY" = "y"; then
sudo apt-get install command
fi
fi
Is this what most people would do? Or is there a more elegant solution?
To check if packagename
was installed, type:
dpkg -s <packagename>
You can also use dpkg-query
that has a neater output for your purpose, and accepts wild cards, too.
dpkg-query -l <packagename>
To find what package owns the command
, try:
dpkg -S `which <command>`
For further details, see article Find out if package is installed in Linux and dpkg cheat sheet.
$name="rsync"
[ `which $name` ] $$ echo "$name : installed" || sudo apt-get install -y $name
This command is the most memorable:
dpkg --get-selections <package-name>
If it's installed it prints:
<package-name> install
Otherwise it prints
No packages found matching <package-name>.
This was tested on Ubuntu 12.04.1 (Precise Pangolin).
apt list [packagename]
seems to be the simplest way to do it outside of dpkg and older apt-* tools
This will do it. apt-get install
is idempotent.
sudo apt-get install command
UpAndAdam wrote:
However you can't simply rely on return codes here for scripting
In my experience you can rely on dkpg's exit codes.
The return code of dpkg -s is 0 if the package is installed and 1 if it's not, so the simplest solution I found was:
dpkg -s <pkg-name> 2>/dev/null >/dev/null || sudo apt-get -y install <pkg-name>
Works fine for me...
In Bash:
PKG="emacs"
dpkg-query -l $PKG > /dev/null || sudo apt install $PKG
Note that you can have a string with several packages in PKG.
This seems to work pretty well.
$ sudo dpkg-query -l | grep <some_package_name> | wc -l
0
if not installed or some number > 0
if installed.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
This explicitly prints 0 if installed else 1 using only awk:
dpkg-query -W -f '${Status}\n' 'PKG' 2>&1|awk '/ok installed/{print 0;exit}{print 1}'
or if you prefer the other way around where 1 means installed and 0 otherwise:
dpkg-query -W -f '${Status}\n' 'PKG' 2>&1|awk '/ok installed/{print 1;exit}{print 0}'
** replace PKG with your package name
Convenience function:
installed() {
return $(dpkg-query -W -f '${Status}\n' "${1}" 2>&1|awk '/ok installed/{print 0;exit}{print 1}')
}
# usage:
installed gcc && echo Yes || echo No
#or
if installed gcc; then
echo yes
else
echo no
fi
I use the following way:
which mySQL 2>&1|tee 1> /dev/null
if [[ "$?" == 0 ]]; then
echo -e "\e[42m MySQL already installed. Moving on...\e[0m"
else
sudo apt-get install -y mysql-server
if [[ "$?" == 0 ]]; then
echo -e "\e[42mMy SQL installed\e[0m"
else
echo -e "\e[42Installation failed\e[0m"
fi
fi
inspired by Chris above
#! /bin/bash
installed() {
return $(dpkg-query -W -f '${Status}\n' "${1}" 2>&1|awk '/ok installed/{print 0;exit}{print 1}')
}
pkgs=(libgl1-mesa-dev xorg-dev vulkan-tools libvulkan-dev vulkan-validationlayers-dev spirv-tools)
missing_pkgs=""
for pkg in ${pkgs[@]}; do
if ! $(installed $pkg) ; then
missing_pkgs+=" $pkg"
fi
done
if [ ! -z "$missing_pkgs" ]; then
cmd="sudo apt install -y $missing_pkgs"
echo $cmd
fi
This feature already exists in Ubuntu and Debian, in the command-not-found
package.
Many things has been told but for me simplest way is:
dpkg -l | grep packagename
To be a little more explicit, here's a bit of bash script that checks for a package and installs it if required. Of course, you can do other things upon finding that the package is missing, such as simply exiting with an error code.
REQUIRED_PKG="some-package"
PKG_OK=$(dpkg-query -W --showformat='${Status}\n' $REQUIRED_PKG|grep "install ok installed")
echo Checking for $REQUIRED_PKG: $PKG_OK
if [ "" = "$PKG_OK" ]; then
echo "No $REQUIRED_PKG. Setting up $REQUIRED_PKG."
sudo apt-get --yes install $REQUIRED_PKG
fi
If the script runs within a GUI (e.g. it is a Nautilus script), you'll probably want to replace the 'sudo' invocation with a 'gksudo' one.
I've found all solutions above can produce a false positive if a package is installed and then removed yet the installation package remains on the system.
To replicate:
Install package apt-get install curl
Remove package apt-get remove curl
Now test above answers.
The following command seems to solve this condition:
dpkg-query -W -f='${Status}\n' curl | head -n1 | awk '{print $3;}' | grep -q '^installed$'
This will result in a definitive installed or not-installed
I had a similar requirement when running test locally instead of in docker. Basically I only wanted to install any .deb files found if they weren't already installed.
# If there are .deb files in the folder, then install them
if [ `ls -1 *.deb 2> /dev/null | wc -l` -gt 0 ]; then
for file in *.deb; do
# Only install if not already installed (non-zero exit code)
dpkg -I ${file} | grep Package: | sed -r 's/ Package:\s+(.*)/\1/g' | xargs dpkg -s
if [ $? != 0 ]; then
dpkg -i ${file}
fi;
done;
else
err "No .deb files found in '$PWD'"
fi
I guess they only problem I can see is that it doesn't check the version number of the package so if .deb file is a newer version, then this wouldn't overwrite the currently installed package.
This one-liner returns 1 (installed) or 0 (not installed) for the 'nano' package..
$(dpkg-query -W -f='${Status}' nano 2>/dev/null | grep -c "ok installed")
even if the package does not exist / is not available.
The example below installs the 'nano' package if it is not installed..
if [ $(dpkg-query -W -f='${Status}' nano 2>/dev/null | grep -c "ok installed") -eq 0 ];
then
apt-get install nano;
fi
It seems that nowadays apt-get
has an option --no-upgrade
that just does what the OP wants:
--no-upgrade
Do not upgrade packages. When used in conjunction with install, no-upgrade will prevent packages listed from being upgraded if they are already installed.
Manpage from https://linux.die.net/man/8/apt-get
Therefore you can use
apt-get install --no-upgrade package
and package
will be installed only if it's not.
I've settled on one based on Nultyi's answer:
MISSING=$(dpkg --get-selections $PACKAGES 2>&1 | grep -v 'install$' | awk '{ print $6 }')
# Optional check here to skip bothering with apt-get if $MISSING is empty
sudo apt-get install $MISSING
Basically, the error message from dpkg --get-selections
is far easier to parse than most of the others, because it doesn't include statuses like "deinstall". It also can check multiple packages simultaneously, something you can't do with just error codes.
Explanation/example:
$ dpkg --get-selections python3-venv python3-dev screen build-essential jq
dpkg: no packages found matching python3-venv
dpkg: no packages found matching python3-dev
screen install
build-essential install
dpkg: no packages found matching jq
So grep removes installed packages from the list, and awk pulls the package names out from the error message, resulting in MISSING='python3-venv python3-dev jq'
, which can be trivially inserted into an install command.
I'm not blindly issuing an apt-get install $PACKAGES
because as mentioned in the comments, this can unexpectedly upgrade packages you weren't planning on; not really a good idea for automated processes that are expected to be stable.
which <command>
if [ $? == 1 ]; then
<pkg-manager> -y install <command>
fi
Use:
apt-cache policy <package_name>
If it is not installed, it will show:
Installed: none
Otherwise it will show:
Installed: version
I offer this update since Ubuntu added its "Personal Package Archive" (PPA) just as this question was answered, and PPA packages have a different result.
Native Debian repository package not installed:
~$ dpkg-query -l apache-perl
~$ echo $?
1
PPA package registered on host and installed:
~$ dpkg-query -l libreoffice
~$ echo $?
0
PPA package registered on host but not installed:
~$ dpkg-query -l domy-ce
~$ echo $?
0
~$ sudo apt-get remove domy-ce
[sudo] password for user:
Reading package lists... Done
Building dependency tree
Reading state information... Done
Package domy-ce is not installed, so not removed
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Also posted on: https://superuser.com/questions/427318/test-if-a-package-is-installed-in-apt/427898
For Ubuntu, apt provides a fairly decent way to do this. Below is an example for google chrome:
apt -qq list google-chrome-stable 2>/dev/null | grep -qE "(installed|upgradeable)" || apt-get install google-chrome-stable
I'm redirecting error output to null because apt warns against using its "unstable cli". I suspect list package is stable so I think it's ok to throw this warning away. The -qq makes apt super quiet.
Source: Stackoverflow.com