web-dev-qa-db-ja.com

Python、サブプロセス、call()、check_callおよびreturncodeを使用して、コマンドが存在するかどうかを確認します

Call()を使用してpythonスクリプトを実行してコマンドを実行する方法を理解しました。

import subprocess

mycommandline = ['lumberjack', '-sleep all night', '-work all day']
subprocess.call(mycommandline)

これは機能しますが、問題があります。ユーザーがコマンドパスに木こりを持っていない場合はどうでしょうか。 lumberjackがpythonスクリプトと同じディレクトリに置かれていれば動作しますが、スクリプトはどのようにしてlumberjackを探すべきかを判断しますか?木こりはコマンドパスにありません。スクリプトは、そのディレクトリが何であるかを見つけ、そこで木こりを探し、最終的にどちらかに見つからない場合は、これら2つの場所のいずれかに木こりをコピーするようユーザーに警告します。エラーメッセージを調べるにはどうすればよいですか?check_call()がエラーメッセージとreturncode属性に関する何かを返すことがあることを読みました。check_call()とreturncodeの使用方法の例が見つかりませんでした。または、メッセージがcommand-not-foundであるかどうかを確認する方法。

私もこれについて正しい方法で行っていますか?

18
Dave Brunker

うわー、それは速かったです! Theodros Zellekeの単純な例とstevehaの関数の使用を、OSErrorに関するabarnertコメントと、ファイルの移動に関するLattywareのコメントと組み合わせました。

import os, sys, subprocess

def nameandpath():
    try:
        subprocess.call([os.getcwd() + '/lumberjack']) 
        # change the Word lumberjack on the line above to get an error
    except OSError:
        print('\nCould not find lumberjack, please reinstall.\n')
        # if you're using python 2.x, change the () to spaces on the line above

try:
    subprocess.call(['lumberjack'])
    # change the Word lumberjack on the line above to get an error
except OSError:
    nameandpath()

Mac OS-X(6.8/Snow Leopard)、Debian(Squeeze)、Windows(7)でテストしました。 3つすべてのオペレーティングシステムで、私が望んでいた方法で機能するように見えました。 check_callとCalledProcessErrorを使用してみましたが、何をしても、毎回エラーが発生するようで、エラーを処理するスクリプトを取得できませんでした。スクリプトをテストするために、スクリプトでディレクトリに木こりがあったため、名前を「lumberjack」から「deadparrot」に変更しました。

このスクリプトの記述方法に問題はありますか?

2
Dave Brunker

簡単なスニペット:

try:
    subprocess.check_call(['executable'])
except subprocess.CalledProcessError:
    pass # handle errors in the called executable
except OSError:
    pass # executable not found
23

コマンドが見つからない場合、subprocessは例外OSErrorを発生させます。

コマンドが見つかり、subprocessがコマンドを実行すると、コマンドから結果コードが返されます。標準では、コード0は成功を意味し、失敗はゼロ以外のエラーコードです(これはさまざまです。実行している特定のコマンドのドキュメントを確認してください)。

したがって、OSErrorをキャッチすると、存在しないコマンドを処理でき、結果コードを確認すると、コマンドが成功したかどうかを確認できます。

subprocessの素晴らしい点は、stdoutstderrからすべてのテキストを収集できることです。そして、それを破棄するか、返すか、ログに記録するか、表示することができます。好きなように。 stderrからのテキストが出力される場合にコマンドが失敗しない限り、コマンドからのすべての出力を破棄するラッパーをよく使用します。

ユーザーに実行可能ファイルをコピーするように依頼するべきではないことに同意します。プログラムは、PATH変数にリストされているディレクトリにある必要があります。プログラムが見つからない場合はインストールするか、PATHにないディレクトリにインストールする場合、ユーザーはPATHを更新してそのディレクトリを含める必要があります。

実行可能ファイルへのさまざまなハードコーディングされたパスでsubprocessを複数回試すオプションがあることに注意してください。

import os
import subprocess as sp

def _run_cmd(s_cmd, tup_args):
    lst_cmd = [s_cmd]
    lst_cmd.extend(tup_args)
    result = sp.call(lst_cmd)
    return result

def run_lumberjack(*tup_args):
    try:
        # try to run from /usr/local/bin
        return _run_cmd("/usr/local/bin/lumberjack", tup_args)
    except OSError:
        pass

    try:
        # try to run from /opt/forest/bin
        return _run_cmd("/opt/forest/bin/lumberjack", tup_args)
    except OSError:
        pass

    try:
        # try to run from "bin" directory in user's home directory
        home = os.getenv("HOME", ".")
        s_cmd = home + "/bin/lumberjack"
        return _run_cmd(s_cmd, tup_args)
    except OSError:
        pass

    # Python 3.x syntax for raising an exception
    # for Python 2.x, use:  raise OSError, "could not find lumberjack in the standard places"
    raise OSError("could not find lumberjack in the standard places")

run_lumberjack("-j")

編集:それについて少し考えた後、私は上記を完全に書き直すことにしました。場所のリストを渡すだけで、代わりの場所が機能するまでループを試すことができます。しかし、ユーザーのホームディレクトリの文字列が不要な場合は構築したくなかったので、代わりのリストに呼び出し可能オブジェクトを追加することを合法化しました。これについて質問がある場合は、質問してください。

import os
import subprocess as sp

def try_alternatives(cmd, locations, args):
    """
    Try to run a command that might be in any one of multiple locations.

    Takes a single string argument for the command to run, a sequence
    of locations, and a sequence of arguments to the command.  Tries
    to run the command in each location, in order, until the command
    is found (does not raise OSError on the attempt).
    """
    # build a list to pass to subprocess
    lst_cmd = [None]  # dummy arg to reserve position 0 in the list
    lst_cmd.extend(args)  # arguments come after position 0

    for path in locations:
        # It's legal to put a callable in the list of locations.
        # When this happens, we should call it and use its return
        # value for the path.  It should always return a string.
        if callable(path):
            path = path()

        # put full pathname of cmd into position 0 of list    
        lst_cmd[0] = os.path.join(path, cmd)
        try:
            return sp.call(lst_cmd)
        except OSError:
            pass
    raise OSError('command "{}" not found in locations list'.format(cmd))

def _home_bin():
    home = os.getenv("HOME", ".")
    return os.path.join(home, "bin")

def run_lumberjack(*args):
    locations = [
        "/usr/local/bin",
        "/opt/forest/bin",
        _home_bin, # specify callable that returns user's home directory
    ]
    return try_alternatives("lumberjack", locations, args)

run_lumberjack("-j")
5
steveha