23

I have a simple requirement. I want to define several variables that will correspond to any number of given packages I want to install via a shell script.

Sample code below:

MISC="shutter pidgin"
WEB="apache2 mongodb"

for pkg in $MISC $WEB; do
    if [ "dpkg-query -W $pkg | awk {'print $1'} = """ ]; then
        echo -e "$pkg is already installed"
    else
        apt-get -qq install $pkg
        echo "Successfully installed $pkg"
    fi
done

Everything kinda works, but the logic seems flawed because it's not reliably installing the packages I want. It either says they've been installed already or it's trying to install packages that have already been installed previously.

I've also been trying with command -v or the following:

if [ "dpkg -l | awk {'print $2'} | grep --regexp=^$pkg$ != """ ]; then

And even with the -n and -z flags to check if the returned string was empty. Pretty sure I'm missing some good sense here.

Do you have any idea what I could do to make sure a package is actually installed or not?

Thanks!

1
  • What harm there is, if you call apt-get install for installed packages, too? You could just call apt-get install $MISC $WEB.
    – jarno
    Apr 22, 2015 at 20:25

4 Answers 4

18

Essentially you only need to replace the if condition with

if dpkg --get-selections | grep -q "^$pkg[[:space:]]*install$" >/dev/null; then

It is not possible to use dpkg-query, because it returns true also for packages removed but not purged.

Also I suggest to check the exit code of apt-get before giving the successful message:

if apt-get -qq install $pkg; then
    echo "Successfully installed $pkg"
else
    echo "Error installing $pkg"
fi
5
  • What does &> do?
    – Taymon
    Jul 12, 2013 at 15:10
  • @Taymon: redirect both stdin and stderr to the given file (/dev/null in this case), because we don't need the output, only the exit code. It only works in bash (the first line of the script has to be #!/bin/bash) otherwise use >/dev/null 2>&1.
    – enzotib
    Jul 12, 2013 at 15:14
  • @Taymon: I changed the logic, because I found a flaw in the preceding solution.
    – enzotib
    Jul 12, 2013 at 15:27
  • Beware: if using bash and the pipefail option is set, then the grep -q can generate Heisenbugs. Basically, grep exits before dpkg finishes writing (you want a 0 exit status in that case) so dpkg fails writing to the pipe (which instead generates a non-0 exit status). Either make sure pipefail is not set, or abandon the (probably tiny) efficiency gains of the "-q" option.
    – Ron Burk
    Apr 30, 2015 at 17:50
  • This answer internally produces as many lines as there are packages known by the system (2870 on my machine), to be then filtered by grep (thus potentially slow), while @jarno's answers uses dpkg-query that returns only one line (potentially faster). On a Raspberry Pi 3, the one based on dpkg-query appears (though inconsistently) faster. Jan 3, 2020 at 18:24
5

You can test it by dpkg-query:

if dpkg-query -W -f'${Status}' "$pkg" 2>/dev/null | grep -q "ok installed"; then

Note that * and ? are wildcards, if they appear in $pkg. I guess dpkg-query may print "reinst-required installed" instead of "ok installed", if package is broken and needs to be reinstalled by command apt-get install --reinstall which can be used to install new packages as well.

0
#to check package is installed or not without distribution dependency
#!/bin/bash
read -p "Package Name: " pkg
which $pkg > /dev/null 2>&1
if [ $? == 0 ]
then
echo "$pkg is already installed. "
else
read -p "$pkg is not installed. Answer yes/no if want installation_ " request
if  [ $request == "yes" ]
then
yum install $pkg
fi
fi
0

This seems similar to what OP wanted to do so I thought I'd share:

function package_check() {
    # Tell apt-get we're never going to be able to give manual feedback:
    export DEBIAN_FRONTEND=noninteractive
    sudo apt-get update

    #PKG_LIST='build-essential devscripts debhelper'
    #if input is a file, convert it to a string like:
    #PKG_LIST=$(cat ./packages.txt)
    PKG_LIST=$1
    for package in $PKG_LIST; do 
        CHECK_PACKAGE=$(sudo dpkg -l \
        | grep --max-count 1 "$package" \
        | awk '{print$ 2}')
            
        if [[ ! -z "$CHECK_PACKAGE" ]]; then 
            echo "$package" 'IS installed'; 
            pkg_installed="yes"
        else 
            echo "$package" 'IS NOT installed, installing';
            sudo apt-get --yes install --no-install-recommends "$package"

            pkg_installed="no"
            package_install "$package"
        fi
    done
    # Delete cached files we don't need anymore
    sudo apt-get clean
}

Now test it:

$ source ./package_check.bash
$ PACKAGES=$(cat ./packages.txt) && package_check "$PACKAGES"
Hit:1 http://deb.debian.org/debian bullseye-updates InRelease
Hit:2 http://security.debian.org/debian-security bullseye-security InRelease
Hit:3 http://deb.debian.org/debian bullseye InRelease
Hit:4 http://mirrors.rit.edu/mxlinux/mx-packages/mx/repo bullseye InRelease
Reading package lists... Done
build-essential IS installed
devscripts IS installed
debhelper IS installed
2048 IS NOT installed, installing
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following NEW packages will be installed:
  2048

You must log in to answer this question.

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