我正在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

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


当前回答

受到克里斯回答的启发:

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

其他回答

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

dpkg -s <packagename>

您还可以使用dpkg-query,它的输出更简洁,而且还接受通配符。

dpkg-query -l <packagename>

要找到哪个包拥有该命令,请尝试:

dpkg -S `which <command>`

有关更多详细信息,请参见文章“了解Linux中是否安装了软件包”和dpkg备忘单。

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

#!/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兼容性,您可能希望使用命令-v,正如在另一个类似问题中提到的那样。

这样的话, 在上面的代码示例中,$pkgname > /dev/null应该被命令-v $pkgname替换。

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

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

dpkg --get-selections <package-name>

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

<包名称>安装

否则它会打印

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

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

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

而不是输出“未安装”。2>&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:没有找到匹配的包。

注意,只有某些包会留下配置文件。像hello这样更简单的包可以直接从安装到未安装,而不需要——purge。

在Ubuntu 20.10上测试。

Python apt包

在Ubuntu 18.04中有一个预安装的名为apt的Python 3包,它公开了一个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 https://askubuntu.com/questions/423355/how-do-i-check-if-a-package-is-installed-on-my-server