Вопрос или проблема
Существует ли способ (из скрипта) определить менеджер пакетов по умолчанию в системе?
Чтобы уточнить, что я хочу сделать: запустить определенную команду, и на Debian или любом из его производных она вернет что-то вроде “apt”, на openSUSE вернет “zypp”, на Fedora и т. д. вернет “yum”, на Arch Linux вернет “pacman” и т. д.
Я знаю, что могу сделать это с помощью чего-то вроде следующего, но мне интересно, есть ли более надежный метод, который не сломается, как только появится исполняемый файл с таким же именем.
which apt >/dev/null 2>&1
if [ $? -eq 0 ]
then
echo "apt"
fi
# и так далее...
Вместо идентификации программных бинарников, вам следует начать с определения дистрибутивов.
Вот несколько строк, которые работают в bash-скриптах:
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
Хотя эти файлы могут быть модифицированы так, что этот метод потерпит неудачу, это будет высоконестандартная конфигурация, поэтому это должно работать в большинстве ситуаций.
Я решил пойти по этому пути после изучения других. Это возникло у меня при запуске множества docker-контейнеров и необходимости в curl / jq, без уверенности в том, что они доступны из задания в задание.
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
На чистом 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
Вы также можете протестировать статус выхода command -v
напрямую:
if command -v apk &> /dev/null
then
sudo apk add ...
fi
Начните с принятого ответа на этот вопрос: Как получить имя дистрибутива и номер версии в простом shell-скрипте?. После этого решите, какой менеджер пакетов вы хотите использовать на основе обнаруженного дистрибутива.
Я использую эту функцию для предоставления зависимостей. Я определяю менеджер пакетов и их использование.
check_dependencies(){
#Определить список зависимостей
declare -Ag deps=([docker]='docker.io' [wg]='wireguard-tools' [lsof]='lsof')
#Определить список менеджеров пакетов и их использование
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')
#Найти менеджер пакетов в системе и установить пакет
install_deps(){
for packman in ${!packman_list[@]}
do
which $packman &>/dev/null && eval $(echo ${packman_list[$packman]} "$*")
done
}
#Найти отсутствующие пакеты из списка зависимостей
declare -ag missing_deps=()
for pack in ${!deps[@]}
do
which $pack &>/dev/null || missing_deps+=(${deps[$pack]})
done
#Установить отсутствующие зависимости
test -z ${missing_deps[0]} || install_deps ${missing_deps[@]} || fail "Dependencies could not provide"
}
Я взял несколько идей отсюда и однострочник из другого места, чтобы получить дистрибутив, и у вас получилось это. Я использую это как часть сборок образов контейнеров. Это позволяет мне выполнять “общие” установки без необходимости заранее знать дистрибутив базового образа. Конечно, если имя пакета варьируется от дистрибутива к дистрибутиву, этот универсальный метод не работает так, как есть.
#!/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 "$@"
Вы можете выполнять его напрямую или подключать и вызывать функцию. Я не использую CentOS или Red Hat, но вы можете добавить их записи в массив или удалить те, которые не хотите поддерживать.
Мне пришлось написать версию POSIX (поскольку некоторые базовые образы, такие как Alpine, не имеют bash по умолчанию), так что вот она
#!/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 "$@"
Добавляю этот скрипт, чтобы перечислить все* установленные менеджеры пакетов, на случай, если это кому-то будет полезно. * Возможно, я упустил некоторые.
#!/usr/bin/env bash
echo Installed package managers:
echo
for pm in apk app apt apt-get cargo dnf dpkg emerge eopkg flatpak nala moss nix-env pacman pamac portage rpm snap xbps yay yum zypper; do
command -v $pm
done
Ответ или решение
Идентификация системного пакетного менеджера в большинстве дистрибутивов Linux может быть сложной задачей, особенно когда требуется использовать автоматические скрипты для установки программ. Знание того, какой пакетный менеджер использовать на конкретной системе, является ключевым аспектом для автоматизации процессов установки ПО, особенно при работе с контейнерами или в окружении, где заранее неизвестно, на каком дистрибутиве будет запущен скрипт.
Теория (T)
Менеджеры пакетов — это инструменты, позволяющие автоматизировать процесс установки, обновления и удаления программного обеспечения на операционной системе. Каждая Linux-система, как правило, использует один или несколько пакетных менеджеров, специфичных для ее дистрибутива. Например, apt
используется в Debian и его производных (таких как Ubuntu), yum
или dnf
в Fedora и CentOS, pacman
в Arch Linux, zypper
в openSUSE и так далее.
Пример (E)
Представим сценарий, при котором IT-специалисту необходимо автоматически определить, какой менеджер пакетов используется на текущей системе, и на основе этой информации выполнить команду установки определенных пакетов. В случае с пакетом управления конфигурациями, например, может потребоваться установка таких инструментов, как curl
или wget
, без необходимости вручную проверять и адаптировать скрипт под каждый дистрибутив.
Проблема в том, что использование команды which <менеджер пакетов>
не всегда надежно, так как на системе может оказаться установленными несколько менеджеров пакетов, и они могут быть не настроены должным образом (например, в контейнерах). Вместо этого рекомендованный подход заключается в определении дистрибутива на основе информации из специфических для системы файлов.
Применение (A)
Для надежного определения менеджера пакетов можно использовать следующий подход. Скрипт идентифицирует дистрибутив, сначала анализируя содержимое файлов, таких как /etc/os-release
, либо используя специальную карту соответствия файлов релизов соответствующим менеджерам пакетов. Ниже представлен пример такого скрипта на языке bash:
#!/bin/bash
# Создаем ассоциативный массив для соответствия файлов дистрибутива менеджерам пакетов
declare -A osInfo;
osInfo[/etc/redhat-release]=yum
osInfo[/etc/arch-release]=pacman
osInfo[/etc/gentoo-release]=emerge
osInfo[/etc/SuSE-release]=zypper
osInfo[/etc/debian_version]=apt
for f in ${!osInfo[@]}
do
if [[ -f $f ]]; then
echo "Менеджер пакетов: ${osInfo[$f]}"
break
fi
done
Данный скрипт читает файлы, специфичные для дистрибутива, и на их основе выводит используемый менеджер пакетов. Это наилучшим образом подходит для определенных случаев, помогая избежать проблем, возникающих при наличии более одного менеджера на системе.
Расширенное использование
Для работы в различных окружениях, например, в Docker-контейнерах, где важно автоматизировать процесс установки, использование комбинации command -v
для определения доступного менеджера пакетов также может быть полезно:
packagesNeeded="curl jq"
if command -v apt-get &>/dev/null; then
sudo apt-get update -y && sudo apt-get install -y $packagesNeeded
elif command -v dnf &>/dev/null; then
sudo dnf install -y $packagesNeeded
elif command -v pacman &>/dev/null; then
sudo pacman -Sy --noconfirm $packagesNeeded
# Добавьте дополнительные проверки для других менеджеров, если требуется
else
echo "Менеджер пакетов не найден. Ручная установка пакетов: $packagesNeeded">&2
fi
Таким образом, правильный подход к идентификации системного пакетного менеджера, основанный на анализе дистрибутива, обеспечивает более надежную и универсальную работу скриптов и автоматических процессов установки программного обеспечения в разных дистрибутивах Linux.