如何检查软件包是否安装,如果没有安装,如何进行安装?

我正在Ubuntu系统上工作,目前我正在做的是:

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

大多数人都会这么做吗?还是有更优雅的解决方案?

302002 次浏览

要检查是否安装了packagename,输入:

dpkg -s <packagename>

你也可以使用dpkg-query,它为你的目的提供更整洁的输出,并且也接受通配符。

dpkg-query -l <packagename>

要找到拥有command的包,请尝试:

dpkg -S `which <command>`

有关更多详细信息,请参见文章查看Linux系统中是否安装了软件包DPKG备忘单

这个特性已经存在于Ubuntu和Debian的command-not-found包中。

为了更明确一点,这里有一些Bash脚本,用于检查包并在需要时安装它。当然,您可以在发现包丢失时执行其他操作,例如简单地使用错误代码退出。

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

如果脚本在GUI中运行(例如,它是鹦鹉螺脚本),您可能需要用'gksudo'调用替换'sudo'调用。

Ubuntu增加了它的“个人软件包档案”;(PPA)和PPA包的结果不同。

  1. 原生Debian存储库包未安装:

    ~$ dpkg-query -l apache-perl
    ~$ echo $?
    1
    
  2. 在主机上注册并安装的PPA包:

    ~$ dpkg-query -l libreoffice
    ~$ echo $?
    0
    
  3. PPA包在主机上注册,但未安装:

    ~$ 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.
    

也发布在:测试APT中是否安装了一个包 . 测试APT中是否安装了一个包

这似乎很有效。

$ sudo dpkg-query -l | grep <some_package_name> | wc -l
  • 它要么返回0(如果未安装),要么返回某个> 0(如果已安装)。

这个命令是最容易记住的:

dpkg --get-selections <package-name>

如果已经安装,它会打印:

& lt;包名称>安装

否则它会打印

没有找到与<package-name>匹配的包。

这是在Ubuntu 12.04.1 (Precise穿山甲)上测试的。

这一行代码为“nano”包返回1(已安装)或0(未安装)…

$(dpkg-query -W -f='${Status}' nano 2>/dev/null | grep -c "ok installed")

即使包不存在或不可用。

下面的例子将安装'nano'包,如果它没有安装…

if [ $(dpkg-query -W -f='${Status}' nano 2>/dev/null | grep -c "ok installed") -eq 0 ];
then
apt-get install nano;
fi

UpAndAdam写道:

但是,不能简单地依赖这里的返回代码来编写脚本

我的经验中,你可以依赖于dkpg的退出码。

如果包已安装,dpkg - s的返回码为0,如果未安装则返回1,因此我找到的最简单的解决方案是:

dpkg -s <pkg-name> 2>/dev/null >/dev/null || sudo apt-get -y install <pkg-name>

这对我来说很好……

$name="rsync"


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

这个就行了。apt-get install是幂等的。

sudo apt-get install --no-upgrade command

我发现在之前的答案中,如果安装了一个包,然后删除了它,但是安装包仍然在系统上,那么所有的解决方案都可能产生假阳性。

复制:

安装包apt-get install curl
删除包apt-get remove curl

现在测试一下前面的答案。

下面的命令似乎可以解决这个问题:

dpkg-query -W -f='${Status}\n' curl | head -n1 | awk '{print $3;}' | grep -q '^installed$'

这将导致确定的安装没有安装

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

使用:

apt-cache policy <package_name>

如果没有安装,它将显示:

Installed: none

否则它将显示:

Installed: version
apt list [packagename]

似乎是在dpkg和旧的恰当的-*工具之外最简单的方法。

对于Ubuntu, apt提供了一种相当不错的方法来做到这一点。下面是谷歌Chrome浏览器的一个例子:

apt -qq list google-chrome-stable 2>/dev/null | grep -qE "(installed|upgradeable)" || apt-get install google-chrome-stable

我将错误输出重定向为null,因为apt警告不要使用它的“不稳定的”。我怀疑列表包是稳定的,所以我认为可以扔掉这个警告。qq让apt变得超级安静。

我已经确定了一个基于Nultyi的回答的:

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

基本上,来自dpkg --get-selections的错误消息比其他大多数错误消息更容易解析,因为它不包括&;deinstall&;这样的状态。它还可以同时检查多个包,这是仅使用错误代码无法做到的。

解释/例子:

$ 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

因此grep从列表中删除已安装的包,而awk从错误消息中提取包名,导致MISSING='python3-venv python3-dev jq',它可以简单地插入到安装命令中。

我不是盲目地发布apt-get install $PACKAGES,因为正如评论中提到的,这可能会意外地升级你没有计划的包;对于期望稳定的自动化流程来说,这并不是一个好主意。

在本地而不是Docker中运行测试时,我也有类似的需求。基本上,我只想安装任何尚未安装的.deb文件。

# 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

我想我能看到的唯一问题是它没有检查包的版本号,所以如果.deb文件是一个更新的版本。这样就不会覆盖当前安装的包。

dpkg-query --showformat='${db:Status-Status}'

这将产生一个不大可能改变的小输出字符串,并且很容易在没有grep的情况下进行确定性比较:

pkg=hello
status="$(dpkg-query -W --showformat='${db:Status-Status}' "$pkg" 2>&1)"
if [ ! $? = 0 ] || [ ! "$status" = installed ]; then
sudo apt install $pkg
fi

$? = 0检查是必要的,因为如果你以前从未安装过一个包,并且在你删除某些包(如hello)后,dpkg-query以状态1退出并输出到stderr:

dpkg-query: no packages found matching hello

而不是输出not-installed2>&1也会捕获该错误消息,当它阻止它到达终端时。

对于多个软件包:

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

可能的状态在man dpkg-query中被记录为:

   n = Not-installed
c = Config-files
H = Half-installed
U = Unpacked
F = Half-configured
W = Triggers-awaiting
t = Triggers-pending
i = Installed

单字母版本可以通过db:Status-Abbrev获得,但它们与动作和错误状态一起出现,所以你得到3个字符,需要将其切割。

因此,我认为依赖非大写状态(Config-files vs config-files)不改变是足够可靠的。

dpkg -s退出状态

不幸的是,这并不是大多数用户想要的:

pkgs='qemu-user pandoc'
if ! dpkg -s $pkgs >/dev/null 2>&1; then
sudo apt-get install $pkgs
fi

因为对于一些包,例如certbot,执行:

sudo apt install certbot
sudo apt remove certbot

使certbot处于config-files状态,这意味着配置文件留在机器中。在这种状态下,dpkg -s仍然返回0,因为包元数据仍然保留,以便那些配置文件可以被更好地处理。

要实际使dpkg -s返回所需的1,将需要--purge:

sudo apt remove --purge certbot

实际上是将它移动到not-installed/dpkg-query: no packages found matching

注意,只有某些包会留下配置文件。像hello这样更简单的包直接从installednot-installed,不需要--purge

在Ubuntu 20.10上测试。

Python apt

在Ubuntu 18.04中有一个预先安装的Python 3包,名为apt,它公开了一个Python apt接口!

检查包是否已安装并在未安装时安装它的脚本可以在:如何使用python-apt API安装包中看到

以下是一份副本供参考:

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

检查可执行文件是否在PATH

看:如何从Bash脚本中检查程序是否存在?

另请参阅

  • https://askubuntu.com/questions/165951/dpkg-get-selections-shows-packages-marked-deinstall < a href = " https://askubuntu.com/questions/165951/dpkg-get-selections-shows-packages-marked-deinstall " > < / >
  • https://askubuntu.com/questions/423355/how-do-i-check-if-a-package-is-installed-on-my-server < a href = " https://askubuntu.com/questions/423355/how-do-i-check-if-a-package-is-installed-on-my-server " > < / >

我使用以下方法:

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

似乎现在apt-get有一个选项--no-upgrade,它只做OP想要的:

--no-upgrade不升级包。当与install一起使用时,no-upgrade将阻止已经安装的包被升级。

来自https://linux.die.net/man/8/apt-get的Manpage

因此你可以使用

apt-get install --no-upgrade package

package只有在未被安装时才会被安装。

如果只使用awk安装了else 1,则显式打印0:

dpkg-query -W -f '${Status}\n' 'PKG' 2>&1|awk '/ok installed/{print 0;exit}{print 1}'

或者如果你喜欢另一种方式,1表示安装,0表示:

dpkg-query -W -f '${Status}\n' 'PKG' 2>&1|awk '/ok installed/{print 1;exit}{print 0}'

**用您的包名替换PKG

便利功能:

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

克里斯回答启发:

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

我使用这个解决方案,因为我发现它是最直接的。

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
}

简洁的事情是,must_install返回1或0,然后被调用if解释为true或false,因此使用[]不需要任何test

install_if接受以空格分隔的任意数量的包。

问题是apt不用于脚本,所以这可能在任何时候停止工作。8)

有点以你的为基础,只是更“优雅”一点。只是因为我很无聊。

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

建议使用以下内容的答案:

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

是正确的。

但是如果你已经安装了dpkg-dev包,并且你不只是想检查一个包是否安装了,而且你还:

  • 想知道一个包是否安装在某个版本
  • 希望在特定的体系结构中拥有一个包
  • 查看是否提供了虚拟包

那么你可以滥用dpkg-checkbuilddeps工具来完成这项工作:

dpkg-checkbuilddeps -d apt /dev/null

这将检查是否安装了apt。

下面将检查apt是否至少安装在2.3.15版本,grep是否作为amd64安装,并且某些已安装的包提供了虚拟包x-window-manager:

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

dpkg-checkbuilddeps的退出状态将告诉脚本依赖项是否满足。因为这个方法支持传递多个包,所以即使你想检查是否安装了多个包,你也只需要运行dpkg-checkbuilddeps一次。

既然你提到了Ubuntu,并且你想以编程的方式做到这一点(虽然也可以使用dpkg的变体,但实现起来会更复杂),这个(which)肯定可以工作:

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

尽管为了POSIX兼容性,你会想要使用command -v来代替另一个类似的问题。中提到的

这样的话, 在上面的代码样例中,which $pkgname > /dev/null应该被command -v $pkgname取代

所有的答案都很好,但是对于像我这样的初学者来说似乎很复杂。这就是对我有效的解决方案。我的Linux环境是centOS,但不能确定它适用于所有发行版

PACKAGE_NAME=${PACKAGE_NAME:-node}


if ! command -v $PACKAGE_NAME > /dev/null; then
echo "Installing $PACKAGE_NAME ..."
else
echo "$PACKAGE_NAME already installed"
fi

如果您的包具有命令行接口,则可以在安装之前通过调用它的命令行工具来计算输出,从而检查包是否存在。

这里有一个叫做helm的包的例子。

#!/bin/bash


# Call the command for the package silently
helm > /dev/null


# Get the exit code of the last command
command_exit_code="$(echo $?)"


# Run installation if exit code is not equal to 0
if [ "$command_exit_code" -ne "0" ]; then
# Package does not exist: Do the package installation
else
echo "Skipping 'helm' installation: Package already exists"
fi;