29

Is there a way (from a script) to identify the default system package manager?

To clarify, what I want to do is run a given command and, on Debian or any of its derivatives it'll return something like "apt", on openSUSE it'll return "zypp", on Fedora et al it'll return "yum", on Arch Linux it'll return "pacman" etc.

I know I can do this with something like the following, I just wondered if there was a more robust method that won't break as soon as there is an executable with the same name.

which apt >/dev/null 2>&1
if [ $? -eq 0 ]
then
    echo "apt"
fi
# etc...
jordanm
  • 42,678
DrAl
  • 393

5 Answers5

23

Instead of identifying binary programs, you should start from identifying distributions.

Hare are a few lines that works in bash scripting:

declare -A osInfo;
osInfo[/etc/redhat-release]=yum
osInfo[/etc/arch-release]=pacman
osInfo[/etc/gentoo-release]=emerge
osInfo[/etc/SuSE-release]=zypp
osInfo[/etc/debian_version]=apt-get
osInfo[/etc/alpine-release]=apk

for f in ${!osInfo[@]} do if [[ -f $f ]];then echo Package manager: ${osInfo[$f]} fi done

Althrough it is possible for these files to be modified so that this method will fail, that would be a highly non-standard configuration so this should work in most situations.

AdminBee
  • 22,803
daisy
  • 54,555
10

Start with the accepted answer to this question: How can I get distribution name and version number in a simple shell script?. Then, decide which package manager you want to use based on the detected distribution.

Jim Paris
  • 14,337
  • Is there a readily available list of package managers for different distributions? There are a lot of debian clones in particular. – DrAl Aug 23 '12 at 06:58
  • Not sure about a readily available list, but all Debian clones will use apt-get. – Jim Paris Aug 23 '12 at 16:52
  • Yes, I realise that; my problem isn't so much identifying the distribution (although your link will certainly make this easier), it's figuring out how to link a distribution name like (to pick one at random) "SolusOS" with apt without maintaining a list of every distribution that exists. – DrAl Aug 24 '12 at 06:49
  • Do it the autotools way: Check if it works. BTW, that a distribution uses apt means that it is a Debian relative (and they are a rather compact bunch), finding yum doesn't mean that the packaging conventions (split into library/development, one big brick of Perl or individual packages off CPAN, ...) are the same. Don't even try to install a Fedora package on CentOS (there are at least 3 or 4 years of version skew, just won't work). – vonbrand Jan 18 '13 at 03:28
  • 1
    Detecting the distro will only work for the popular ones. A whitelist can't be updated for all the less know distributions. Directly testing the presence of the tools is the only way to make it work. If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck. – liberforce Oct 18 '18 at 09:53
10

I opted to go this route after reviewing the others. This came up for me when running many docker containers and needing curl / jq and not being able to rely on what was available from job to job.

script:
 - packagesNeeded='curl jq'
 - if [ -x "$(command -v apk)" ];       then sudo apk add --no-cache $packagesNeeded
 - elif [ -x "$(command -v apt-get)" ]; then sudo apt-get install $packagesNeeded
 - elif [ -x "$(command -v dnf)" ];     then sudo dnf install $packagesNeeded
 - elif [ -x "$(command -v zypper)" ];  then sudo zypper install $packagesNeeded
 - else echo "FAILED TO INSTALL PACKAGE: Package manager not found. You must manually install: $packagesNeeded">&2; fi

In pure bash:

packagesNeeded=(curl jq)
if [ -x "$(command -v apk)" ];
then
    sudo apk add --no-cache "${packagesNeeded[@]}"
elif [ -x "$(command -v apt-get)" ];
then
    sudo apt-get install "${packagesNeeded[@]}"
elif [ -x "$(command -v dnf)" ];
then
    sudo dnf install "${packagesNeeded[@]}"
elif [ -x "$(command -v zypper)" ];
then
    sudo zypper install "${packagesNeeded[@]}"
else
    echo "FAILED TO INSTALL PACKAGE: Package manager not found. You must manually install: "${packagesNeeded[@]}"">&2;
fi

You can also test the exit status of command -v directly:

if command -v apk &> /dev/null
then
    sudo apk add ...
fi
muru
  • 72,889
Mark
  • 201
  • I'll note in GitLab CI I had some issue stacking the 'elif' for some reason which is why I just if statements. – Mark May 05 '20 at 21:23
1

I am using this function to provide dependencies. I am defining package manager and their how to use.

check_dependencies(){
    #Declare list of dependencies
    declare -Ag deps=([docker]='docker.io' [wg]='wireguard-tools' [lsof]='lsof')
#Declare list of package managers and their usages
declare -Ag packman_list=([pacman]='pacman -Sy' [apt]='echo "deb http://deb.debian.org/debian buster-backports main contrib non-free" > /etc/apt/sources.list.d/buster-backports.list; apt update -y; apt install -y' [yum]='yum install -y epel-release; yum repolist -y; yum install -y')

#Find the package manager on the system and install the package
install_deps(){
    for packman in ${!packman_list[@]}
    do
        which $packman &>/dev/null && eval $(echo ${packman_list[$packman]} "$*")
    done
}

#Find the missing packages from list of dependencies
declare -ag missing_deps=()
for pack in ${!deps[@]}
do
    which $pack &>/dev/null || missing_deps+=(${deps[$pack]})
done

#Install missing dependencies
test -z ${missing_deps[0]} || install_deps ${missing_deps[@]} || fail "Dependencies could not provide"

}

0

I took some ideas from here and a one liner from elsewhere to get the distro and you have this. I'm using this as part of container image builds. Let's me do "generic" installs without having to know apriori the distro of the base image. Of course if a package name varies from distro to distro this one fits all fails as is.

#!/bin/bash
pkg_install () {

local distro;local cmd;local usesudo declare -A pkgmgr pkgmgr=(
[arch]="pacman -S --noconfirm"
[alpine]="apk add --no-cache"
[debian]="apt-get install -y"
[ubuntu]="apt-get install -y"
)

distro=$(cat /etc/os-release | tr [:upper:] [:lower:] | grep -Poi '(debian|ubuntu|red hat|centos|arch|alpine)' | uniq) cmd="${pkgmgr[$distro]}" [[ ! $cmd ]] && return 1 if [[ $1 ]]; then [[ ! $EUID -eq 0 ]] && usesudo=sudo echo installing packages command: $usesudo $cmd $@ $usesudo $cmd $@ else echo $cmd fi }

if script was executed then call the function

(return 0 2>/dev/null) || pkg_install "$@"

you can run it directly or source it and call the function. I don't use centos or redhat but you can add their entries to the array, or remove ones you don't want to support.

I had to write a posix version (because some base images like alpine don't have bash installed by default) so here is that

#!/bin/sh
hm_hash() {
    echo "$1" | md5sum -
}

hm_put() { echo "$3" > "$1/$(hm_hash "$2")" }

hm_get() { cat "$1/$(hm_hash "$2")" }

pkg_install () {

local distro;local cmd;local usesudo; local pkgmgr pkgmgr="$(mktemp -d)" hm_put "$pkgmgr" arch "pacman -S --noconfirm" hm_put "$pkgmgr" alpine "apk add --no-cache" hm_put "$pkgmgr" debian "apt-get install -y" hm_put "$pkgmgr" ubuntu "apt-get install -y"

distro=$(cat /etc/os-release | tr [:upper:] [:lower:] | grep -Poi '(debian|ubuntu|red hat|centos|arch|alpine)' | uniq) cmd=$(hm_get "$pkgmgr" $distro 2> /dev/null) rm -rf $pkgmgr if [ ! -z "$cmd" ]; then if [ -z "$1" ]; then echo $cmd else echo installing packages command: $cmd $@ $cmd $@ fi else
return 1
fi
}

if script was executed then call the function

(return 0 2>/dev/null) || pkg_install "$@"

DKebler
  • 282