5
\$\begingroup\$

I was able to use dpkg-query to determine if a package is installed on Ubuntu, but it's a bit of a mess because the name used to install the package doesn't always align with what it's installed as. For instance, gnutls-dev is installed under libgnutls-dev...

Any suggestions on how I could clean this up a bit?

function isInstalled() {
    if dpkg-query -W -f'${Status}' "$1" 2>/dev/null | grep -q "ok installed" || dpkg-query -W -f'${Status}' "lib$1" 2>/dev/null | grep -q "ok installed"; then
        echo 1;
    fi

    echo 0
}
\$\endgroup\$
  • \$\begingroup\$ Do you mean that you install using a name that the actual package Provides? So that a different package is installed than what was requested? \$\endgroup\$ – Toby Speight Dec 19 '17 at 14:49
  • \$\begingroup\$ I'm saying if you run apt install gnutls-dev it's installed but is listed by dpkg-query as libgnutls-dev... gnutls-dev is a virtual package, I guess. But either way, the code essentially does what it needs to for the sake of my project, just curious if there's a better way to do it. \$\endgroup\$ – user1960364 Dec 19 '17 at 14:55
  • \$\begingroup\$ What's the intended usage? Where would you get the package name to test? \$\endgroup\$ – 200_success Dec 20 '17 at 3:46
  • \$\begingroup\$ Using it as part of a custom install script, package names are hard-coded into the script. Just using this function as a way of adding a visual indicator for which of the required dependencies are already installed or need to be installed. \$\endgroup\$ – user1960364 Dec 20 '17 at 4:42
3
\$\begingroup\$

It seems odd that the function prints 0 in all cases, including when 1 has been printed.

For a utility like this, it's probably better to communicate via the exit status:

isInstalled() {
    !   dpkg-query -W -f'${Status}' "$1" 2>/dev/null | grep -q "ok installed" \
     || dpkg-query -W -f'${Status}' "lib$1" 2>/dev/null | grep -q "ok installed"
}

I'd be inclined to remove the !, returning zero (success) if the package is found.

If you have aptitude available, it's possible to ask it to search for any installed package of the given name, or providing the name:

aptitude -q2 search "~i~P?exact-name($1)|~i?exact-name($1)" >/dev/null
  • ~i: installed package
  • ~P: provides name
  • |: alternation
  • ^$1\$
  • ?exact-name: exactly what it says

This is less brittle, as it doesn't rely on the providing package being formed by prepending lib to the virtual package name. For example, with www-browser, it finds all installed web browsers. Also, it avoids matching superstrings of the supplied name.

If you know that $1 will always be the name of a virtual package, you can of course use just the ~P side of the alternation.

\$\endgroup\$
  • \$\begingroup\$ Interesting approach on using aptitude, it seems a lot more reliable... but it is significantly slower. \$\endgroup\$ – user1960364 Dec 19 '17 at 23:10
  • \$\begingroup\$ Yes, it does spend a lot of time rebuilding its databases; I should have mentioned that. \$\endgroup\$ – Toby Speight Dec 20 '17 at 9:18
2
\$\begingroup\$

the name used to install the package doesn't always align with what it's installed as. For instance, gnutls-dev is installed under libgnutls-dev

That's because they are different things:

  • gnutls-dev is a single command/utility
  • libgnutls-dev is a package

Note that one single package can contain various commands. I'd say the most notable cases are coreutils and util-linux.

So to check if a command or package is installed, you'd need to follow different approaches.

For example (assuming a recent version of Bash):

check_command() {
  local 'command' 'not_found'
  for command; do
    if ! type -- "${command}" > '/dev/null' 2>&1; then
      printf '%s\n' "Command not found: ${command}"
      (( not_found++ ))
    fi
  done
  if (( not_found > 0 )); then
    printf '%s\n' "Missing commands: ${not_found}"
    return '1'
  fi
}

check_package() {
  local 'not_found' 'package' 'packages'
  packages="$( dpkg --get-selections | cut -f '1' | sort )"
  for package; do
    if ! grep -P -e "^${package}\$" <<< "${packages}" > '/dev/null' 2>&1; then
      printf '%s\n' "Package not found: ${package}"
      (( not_found++ ))
    fi
  done
  if (( not_found > 0 )); then
    printf '%s\n' "Missing packages: ${not_found}"
    return '1'
  fi
}

Then:

# cat and lsblk are commands
# coreutils and util-linux are packages

$ check_command cat coreutils lsblk util-linux
Command not found: coreutils
Command not found: util-linux
Missing commands: 2

$ check_package cat coreutils lsblk util-linux
Package not found: cat
Package not found: lsblk
Missing packages: 2
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.