ほぼ中立ブログ

少しだけ百合に偏った雑記ブログ

Pythonで外部コマンドとモジュールの有無を確認し無ければインストールする

【スポンサーリンク】

依存関係を手作業で解消するのが面倒だったのでPythonにやってもらうことしました。「退屈なことはPythonにやらせよう」ですね。環境はUbuntuを想定しています。

とはいえ高度なヤツは技量的に無理だったのでaptとpipで簡単にインストールできるものだけですが。

外部コマンド確認(whichみたいな)

Qiitaのこちらの方の記事が大変参考になりました。Python3.3以降ならshutilモジュールのwhich関数が利用できるようです。使用感もLinuxコマンドのものに近く、与えられたコマンドが存在すればそのパスを、無ければNoneを返します。

>>> import shutil
>>> shutil.which('which')
'/usr/bin/which'

debパッケージのインストール確認

コマンド名とパッケージ名が異なる場合、上の方法だと少し都合が悪いため、パッケージ名で直接インストールの有無を確認する方法も調べました。あまり良い方法が見つからなかったので、メッセージが若干邪魔ですが「dpkg -s」コマンドの終了ステータスを利用することにしました。

$ dpkg -s [package]
...
Status: install ok installed
...

モジュールのインストール確認

残念ながらこれも良い方法を見つけられなかったので、単純にpythonコマンドの-cオプションを使用してimportを実行し終了ステータスを調べることにしました。

>>> import subprocess
>>> retval = subprocess.call(["python3", "-c", "import sys"])
>>> retval
0

importが成功すると終了ステータスは0になりますが、失敗するとModuleNotFoundErrorが発生し終了ステータスが1になるのでインストールの確認に利用することができます。エラーメッセージが若干うるさいですが実害がないのでとりあえず放置することにしました。

自動インストールスクリプト

上の内容を踏まえて自動インストールスクリプトを作成してみました。configparserを利用して、aptでインストールするdebパッケージ名とpipでインストールするpythonモジュール名をセクションごとに分けて記載したconfigファイルを受け取るようにしました。以下のような構造です。

[deb package]
package1
...
[python module]
module1
...

上のファイルに書かれたパッケージまたはモジュールのインストールの有無をスクリプト内で確認し、無ければそのままインストールコマンドを実行します。

#!/usr/bin/env python3

import configparser
import subprocess
import sys


def is_installed(package):
    retval = subprocess.call(["dpkg", "-s", package])
    if retval == 0:
        return True
    else:
        return False


def is_importable(module):
    retval = subprocess.call(["python3", "-c", "import {}".format(module)])
    if retval == 0:
        return True
    else:
        return False


def install(config_file):
    config = configparser.ConfigParser(allow_no_value=True)
    config.read(config_file)
    # deb packageのインストール
    for package in config["deb package"]:
        installed = is_installed(package)
        if not installed:
            subprocess.call(["sudo", "apt", "install", package])

    # python moduleのインストール
    for module in config["python module"]:
        importable = is_importable(module)
        if not importable:
            subprocess.call(["sudo", "-H", "pip3", "install", module])


if __name__ == "__main__":
    conf = sys.argv[1]
    install(conf)

最後までお読みいただきありがとうございました。