How can I check if a package is installed and install it if not?

BashApt Get

Bash Problem Overview


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?

Bash Solutions


Solution 1 - Bash

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.

Solution 2 - Bash

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.

Solution 3 - Bash

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 or 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

Solution 4 - Bash

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

Solution 5 - Bash

Ubuntu added its "Personal Package Archive" (PPA), and PPA packages have a different result.

  1. A native Debian repository package is not installed:

    ~$ dpkg-query -l apache-perl
    ~$ echo $?
    1
    
  2. A PPA package registered on the host and installed:

    ~$ dpkg-query -l libreoffice
    ~$ echo $?
    0
    
  3. A PPA package registered on the 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: Test if a package is installed in APT

Solution 6 - Bash

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>

It works fine for me...

Solution 7 - Bash

This seems to work pretty well.

$ sudo dpkg-query -l | grep <some_package_name> | wc -l
  • It either returns 0 if not installed or some number > 0 if installed.

Solution 8 - Bash

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.

Solution 9 - Bash

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.

Solution 10 - Bash

This will do it. apt-get install is idempotent.

sudo apt-get install --no-upgrade command

Solution 11 - Bash

I've found all solutions in previous answers 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 the previous 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.

Solution 12 - Bash

$name="rsync"

[ `which $name` ] $$ echo "$name : installed" || sudo apt-get install -y $name

Solution 13 - Bash

Use:

apt-cache policy <package_name>

If it is not installed, it will show:

Installed: none

Otherwise it will show:

Installed: version

Solution 14 - Bash

Inspired by Chris' answer:

#! /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

Solution 15 - Bash

This feature already exists in Ubuntu and Debian, in the command-not-found package.

Solution 16 - Bash

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).

Solution 17 - Bash

which <command>
if [ $? == 1 ]; then
    <pkg-manager> -y install <command>
fi

Solution 18 - Bash

apt list [packagename]

seems to be the simplest way to do it outside of dpkg and older apt-* tools.

Solution 19 - Bash

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.

Solution 20 - Bash

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 the 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.

Solution 21 - Bash

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

Solution 22 - Bash

Kinda based off yours just a little more 'elegant'. Just because I'm bored.

#!/bin/bash
FOUND=("\033[38;5;10m")
NOTFOUND=("\033[38;5;9m")
PKG="${@:1}"
command ${PKG} &>/dev/null
if [[ $? != 0 ]]; then
    echo -e "${NOTFOUND}[!] ${PKG} not found [!]"
    echo -e "${NOTFOUND}[!] Would you like to install ${PKG} now ? [!]"
    read -p "[Y/N] >$ " ANSWER
    if [[ ${ANSWER} == [yY] || ${ANSWER} == [yY][eE][sS] ]]; then
        if grep -q "bian" /etc/os-release; then
            sudo apt-get install ${PKG}
        elif grep -q "arch" /etc/os-release; then
            if [[ -f /bin/yay ]] || [[ -f /bin/yaourt ]]; then
                yaourt -S ${PKG} 2>./err || yay -S ${PKG} 2>./err
            else
                sudo pacman -S ${PKG}
            fi
        elif grep -q "fedora" /etc/os-release; then
             sudo dnf install ${PKG}
        else
            echo -e "${NOTFOUND}[!] This script couldn't detect your package manager [!]"
            echo -e "${NOTFOUND}[!] Manually install it [!]"
        fi
    elif [[ ${ANSWER} == [nN] || ${ANSWER} == [nN][oO] ]]; then
        echo -e "${NOTFOUND}[!] Exiting [!]"
    fi
else
    echo -e "${FOUND}[+] ${PKG} found [+]"
fi

Solution 23 - Bash

The answers that suggest to use something along the lines of:

dpkg-query --showformat '${db:Status-Status}\n' --show $package | grep -q '^installed$'
dpkg-query --showformat '${Status}\n' --show $package | grep -q '^install ok installed$'

are correct.

But if you have the package dpkg-dev installed and you do not just want to check whether a package is installed but you also:

  • want to know whether a package is installed in a certain version
  • want to have a package in a certain architecture
  • want to see if a virtual package is provided

then you can abuse the dpkg-checkbuilddeps tool for this job:

dpkg-checkbuilddeps -d apt /dev/null

This will check whether apt is installed.

The following will check whether apt is installed in at least version 2.3.15 and grep is installed as amd64 and the virtual package x-window-manager is provided by some of the installed packages:

dpkg-checkbuilddeps -d 'apt (>= 2.3.15), grep:amd64, x-window-manager' /dev/null

The exit status of dpkg-checkbuilddeps will tell the script whether the dependencies are satisfied or not. Since this method supports passing multiple packages, you only have to run dpkg-checkbuilddeps once even if you want to check whether more than one package is installed.

Solution 24 - Bash

Since you mentioned Ubuntu, and you want to do this programmatically(although dpkg variations can also be used but would be more complex to implement), this(which) will definitely work:

#!/bin/bash

pkgname=mutt
which $pkgname > /dev/null;isPackage=$?
if [ $isPackage != 0 ];then
        echo "$pkgname not installed"
        sleep 1
        read -r -p "${1:-$pkgname will be installed. Are you sure? [y/N]} " response
        case "$response" in
            [yY][eE][sS]|[yY]) 
                sudo apt-get install $pkgname
                ;;
            *)
                false
                ;;
        esac

else
        echo "$pkgname is installed"
        sleep 1
fi

Although for POSIX compatibility, you would want to use command -v instead as mentioned in another similar question.

In that case, which $pkgname > /dev/null should be replaced by command -v $pkgname in the above code sample.

Solution 25 - Bash

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

Solution 26 - Bash

I use this solution as I find it most straightforward.

function must_install(){
   return "$(apt -qq list $var --installed 2> /dev/null |wc -l)"
}

function install_if() {
    unset install
    for var in "$@"
    do
        if $(must_install $var)
        then
            install+="${var} "
        fi
    done
    if [ -n "$install" ];
    then
        sudo apt-get install -qy $install
    fi
}

The neat thing is, must_install returns 1 or 0 which is then interpreted as true or false from the calling if, so we don't need any test using [].

install_if takes any number of packages separated by space.

The problem is apt is not meant to be used in scripts, so this might stop working at any time. 8)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJohn JiangView Question on Stackoverflow
Solution 1 - Bashviam0ZahView Answer on Stackoverflow
Solution 2 - BashUrhixidurView Answer on Stackoverflow
Solution 3 - BashSebView Answer on Stackoverflow
Solution 4 - BashCiro Santilli Путлер Капут 六四事View Answer on Stackoverflow
Solution 5 - BashtahoarView Answer on Stackoverflow
Solution 6 - Bashrocka84View Answer on Stackoverflow
Solution 7 - BashsandmanView Answer on Stackoverflow
Solution 8 - BashIzkataView Answer on Stackoverflow
Solution 9 - BashGiovanni MascellaniView Answer on Stackoverflow
Solution 10 - BashDavid BaucumView Answer on Stackoverflow
Solution 11 - BashMarkView Answer on Stackoverflow
Solution 12 - BashKyprioView Answer on Stackoverflow
Solution 13 - BashMohammed NoureldinView Answer on Stackoverflow
Solution 14 - BashFreeToGoView Answer on Stackoverflow
Solution 15 - BashcamhView Answer on Stackoverflow
Solution 16 - BashiNultyView Answer on Stackoverflow
Solution 17 - Bashirock.devView Answer on Stackoverflow
Solution 18 - BashErichView Answer on Stackoverflow
Solution 19 - Bashcarlin.scottView Answer on Stackoverflow
Solution 20 - BashCraigView Answer on Stackoverflow
Solution 21 - BashChrisView Answer on Stackoverflow
Solution 22 - BashWaXxX333View Answer on Stackoverflow
Solution 23 - BashjoschView Answer on Stackoverflow
Solution 24 - BashJoyView Answer on Stackoverflow
Solution 25 - BashNitish JadiaView Answer on Stackoverflow
Solution 26 - BashJPTView Answer on Stackoverflow