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

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


当前回答

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

grep -qE "(已安装|upgradeable)"|| apt-get install google-chrome-stable

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

其他回答

这似乎很有效。

$ sudo dpkg-query -l | grep <some_package_name> | wc -l

如果没有安装,则返回0;如果安装,则返回某个数字> 0。

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

grep -qE "(已安装|upgradeable)"|| apt-get install google-chrome-stable

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

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

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,因此我们不需要使用[]进行任何测试。

Install_if接受按空格分隔的任意数量的包。

问题是apt并不打算在脚本中使用,因此这可能在任何时候停止工作。8)

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

在本地而不是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文件是一个更新的版本。这样就不会覆盖当前安装的包。