Tester si l'exécutable existe en Python?

297

En Python, existe-t-il un moyen portable et simple de tester si un programme exécutable existe?

Par simple, je veux dire quelque chose comme la whichcommande qui serait tout simplement parfaite. Je ne veux pas rechercher manuellement PATH ou quelque chose impliquant d'essayer de l'exécuter avec Popen& al et voir s'il échoue (c'est ce que je fais maintenant, mais imaginez que c'est launchmissiles)

Piotr Lesnicki
la source
4
Quel est le problème avec la recherche de la variable d'environnement PATH? Que pensez-vous de la commande UNIX 'which'?
Jay
1
Le script which.py ​​de stdlib est-il un moyen simple?
jfs
@JF - le script which.py ​​incl. avec Python dépend de «ls» et certains des autres commentaires indiquent que Piotr cherchait une réponse multiplateforme.
Jay
@Jay: Merci pour le commentaire. J'ai coreutils installé sur Windows, donc je n'ai pas remarqué que which.py ​​est spécifique à Unix.
jfs
Il y a aussi whichle module tiers: code.activestate.com/pypm/which
Sridhar Ratnakumar

Réponses:

321

La façon la plus simple à laquelle je peux penser:

def which(program):
    import os
    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file

    return None

Edit : exemple de code mis à jour pour inclure la logique de gestion du cas où l'argument fourni est déjà un chemin complet vers l'exécutable, c'est-à-dire "which / bin / ls". Cela imite le comportement de la commande UNIX 'which'.

Edit : mis à jour pour utiliser os.path.isfile () au lieu de os.path.exists () par commentaires.

Edit : path.strip('"')semble être la mauvaise chose à faire ici. Ni Windows ni POSIX ne semblent encourager les éléments PATH cités.

Geai
la source
Merci Jay, j'accepte votre réponse, mais pour moi, elle répond à ma question par la négative. Aucune fonction de ce type n'existe dans les bibliothèques, je dois juste l'écrire (j'avoue que ma formulation n'était pas assez claire dans le fait que je sais ce qui fait).
Piotr Lesnicki
1
Jay, si vous complétez votre réponse en fonction de la mienne (pour avoir un «w» complet) afin que je puisse supprimer la mienne.
Piotr Lesnicki
2
Pour certains systèmes d'exploitation, vous devrez peut-être ajouter l'extension de l'exécutable. Par exemple, sur Ubuntu, je peux écrire lequel ("scp") mais sur Windows, je devais écrire lequel ("scp.exe").
waffleman
13
Je suggère de remplacer "os.path.exists" par "os.path.isfile". Sinon, sous Unix, cela pourrait faussement correspondre à un répertoire avec le bit + x défini. Je trouve également utile d'ajouter ceci en haut de la fonction: import sys; si sys.platform == "win32" et non program.endswith (". exe"): program + = ".exe". De cette façon, sous Windows, vous pouvez faire référence à "calc" ou "calc.exe", tout comme vous le feriez dans une fenêtre cmd.
Kevin Ivarsen du
1
@KevinIvarsen Une meilleure option serait une boucle à travers les valeurs du PATHEXTvar env parce commandest aussi valide que tout command.comcomme scriptcontrescript.bat
Lekensteyn
325

Je sais que c'est une question ancienne, mais vous pouvez l'utiliser distutils.spawn.find_executable. Cela a été documenté depuis python 2.4 et existe depuis python 1.6.

import distutils.spawn
distutils.spawn.find_executable("notepad.exe")

En outre, Python 3.3 propose désormais shutil.which().

Nathan Binkert
la source
7
Sur win32, l' distutils.spawn.find_executableimplémentation recherche uniquement .exeplutôt que d'utiliser la liste d'extensions pour rechercher set in %PATHEXT%. Ce n'est pas génial, mais cela pourrait fonctionner pour tous les cas dont quelqu'un a besoin.
rakslice
7
exemple d'utilisation:from distutils import spawn php_path = spawn.find_executable("php")
codefreak
6
Apparemment, distutils.spawnn'est pas disponible de manière fiable: avec mon installation système (/ usr / bin / python) de Python 2.7.6 sur OS X 10.10, j'obtiens AttributeError: 'module' object has no attribute 'spawn':, bien qu'étrangement, cela fonctionne sur la même machine avec la même version de Python, mais à partir de une installation de virtualenv.
Josh Kupershmidt
8
@JoshKupershmidt, assurez-vous import distutils.spawnou suivez la from distutils import spawnsyntaxe plutôt que juste import distutils. Sinon, il peut ne pas être accessible et vous obtiendrez ce qui précède AttributeErrormême s'il est là.
John
39

Pour python 3.2 et versions antérieures:

my_command = 'ls'
any(os.access(os.path.join(path, my_command), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))

Ceci est une ligne de la réponse de Jay , également ici en tant que fonction lambda:

cmd_exists = lambda x: any(os.access(os.path.join(path, x), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))
cmd_exists('ls')

Ou enfin, en retrait en fonction:

def cmd_exists(cmd):
    return any(
        os.access(os.path.join(path, cmd), os.X_OK) 
        for path in os.environ["PATH"].split(os.pathsep)
    )

Pour python 3.3 et versions ultérieures:

import shutil

command = 'ls'
shutil.which(command) is not None

En tant que doublure de Jan-Philip Gehrcke Answer :

cmd_exists = lambda x: shutil.which(x) is not None

En déf:

def cmd_exists(cmd):
    return shutil.which(cmd) is not None
ThorSummoner
la source
1
la version "indentée en tant que fonction" utilise la variable xlà où elle devrait êtrecmd
0x89
vous devez également ajouter un test pour voir s'il os.path.join(path, cmd)s'agit d'un fichier, non? Après tout, les répertoires peuvent également avoir le bit exécutable défini ...
MestreLion
@MestreLion Cela ressemble à un cas possible, pourriez-vous confirmer ce comportement et mettre à jour cette réponse? Je suis heureux de changer ce post en wiki communautaire si cela aide.
ThorSummoner
1
@ThorSummoner: Je l'ai confirmé, et il nécessite en effet le test de fichier. Un test simple:mkdir -p -- "$HOME"/bin/dummy && PATH="$PATH":"$HOME"/bin && python -c 'import os; print any(os.access(os.path.join(path, "dummy"), os.X_OK) for path in os.environ["PATH"].split(os.pathsep))' && rmdir -- "$HOME"/bin/dummy
MestreLion
1
Ajouter un simple and os.path.isfile(...)aux endroits appropriés suffit à résoudre ce problème
MestreLion
19

N'oubliez pas de spécifier l'extension du fichier sur Windows. Sinon, vous devez écrire une variable beaucoup plus compliquée is_exepour Windows en utilisant PATHEXTla variable d'environnement. Vous voudrez peut-être simplement utiliser FindPath .

OTOH, pourquoi vous dérangez-vous même de rechercher l'exécutable? Le système d'exploitation le fera pour vous dans le cadre de l' popenappel et déclenchera une exception si l'exécutable n'est pas trouvé. Tout ce que vous devez faire est d'attraper l'exception correcte pour un système d'exploitation donné. Notez que sous Windows, subprocess.Popen(exe, shell=True)échouera silencieusement s'il exen'est pas trouvé.


Intégration PATHEXTdans la mise en œuvre ci-dessus de which(dans la réponse de Jay):

def which(program):
    def is_exe(fpath):
        return os.path.exists(fpath) and os.access(fpath, os.X_OK) and os.path.isfile(fpath)

    def ext_candidates(fpath):
        yield fpath
        for ext in os.environ.get("PATHEXT", "").split(os.pathsep):
            yield fpath + ext

    fpath, fname = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            exe_file = os.path.join(path, program)
            for candidate in ext_candidates(exe_file):
                if is_exe(candidate):
                    return candidate

    return None
Suraj
la source
1
Il a corrigé un bug dans la réponse acceptée, sentez que cette réponse devrait être au dessus.
NiTe Luo
l'utilisation intelligente de yieldin ext_candidates, m'a permis de mieux comprendre le fonctionnement de ce mot clé
Grant Humphries
15

Pour les plates-formes * nix (Linux et OS X)

Cela semble fonctionner pour moi:

Édité pour travailler sur Linux, grâce à Mestreion

def cmd_exists(cmd):
    return subprocess.call("type " + cmd, shell=True, 
        stdout=subprocess.PIPE, stderr=subprocess.PIPE) == 0

Ce que nous faisons ici, c'est utiliser la commande intégrée typeet vérifier le code de sortie. S'il n'y a pas une telle commande, se typeterminera par 1 (ou un code d'état différent de zéro de toute façon).

Le bit sur stdout et stderr est juste pour faire taire la sortie de la typecommande, car nous ne sommes intéressés que par le code d'état de sortie.

Exemple d'utilisation:

>>> cmd_exists("jsmin")
True
>>> cmd_exists("cssmin")
False
>>> cmd_exists("ls")
True
>>> cmd_exists("dir")
False
>>> cmd_exists("node")
True
>>> cmd_exists("steam")
False
hasen
la source
2
Êtes-vous sûr que cela fonctionne? C'est une très bonne approche, mais typec'est un shell intégré, pas un fichier exécutable, subprocess.call()échoue donc ici.
MestreLion
1
L'avez-vous essayé ou êtes-vous en train de théoriser? Cela fonctionne de toute façon sur mon mac.
hasen
Je l'ai essayé dans Ubuntu 12.04, ça jette OSError: [Errno 2] No such file or directory. Peut-être que dans Mac typeest une commande réelle
MestreLion
2
Après beaucoup de tests, j'ai trouvé comment réparer: ajouter shell=Trueet remplacer ["type", cmd]pour"type " + cmd
MestreLion
4
Attention: assurez-vous que la variable "cmd" contient des données valides. S'il provient d'une source externe, un méchant pourrait vous donner "ls; rm -rf /". Je pense que la solution in-python (sans sous-processus) est bien meilleure. Point suivant: Si vous appelez souvent cette méthode, la solution de sous-processus est beaucoup plus lente, car de nombreux processus doivent être générés.
guettli
7

Voir le module os.path pour quelques fonctions utiles sur les noms de chemin. Pour vérifier si un fichier existant est exécutable, utilisez os.access (chemin, mode) , avec le mode os.X_OK.

os.X_OK

Valeur à inclure dans le paramètre mode de access () pour déterminer si le chemin peut être exécuté.

EDIT: Il which()manque un indice aux implémentations suggérées - utiliser os.path.join()pour construire des noms de fichiers complets.

gimel
la source
Merci, gimel, donc en gros j'ai ma réponse: aucune fonction n'existe, je dois le faire manuellement.
Piotr Lesnicki
N'utilisez pas os.access. la fonction d'accès est conçue pour les programmes suid.
Changming Sun
6

Sur la base qu'il est plus facile de demander pardon que de permission, j'essayerais simplement de l'utiliser et de détecter l'erreur (OSError dans ce cas - j'ai vérifié que le fichier n'existe pas et que le fichier n'est pas exécutable et qu'ils donnent tous les deux OSError).

Cela aide si l'exécutable a quelque chose comme un --versionindicateur qui est un non-fonctionnement rapide.

import subprocess
myexec = "python2.8"
try:
    subprocess.call([myexec, '--version']
except OSError:
    print "%s not found on path" % myexec

Ce n'est pas une solution générale, mais ce sera le moyen le plus simple pour de nombreux cas d'utilisation - ceux où le code doit rechercher un seul exécutable bien connu.

Hamish Downer
la source
3
C'est même trop dangereux d'appeler --versionun programme nommé launchmissiles!
xApple
1
+1, j'aime cette approche. EAFP est une règle d'or en Python. À l'exception peut-être de la mise en place de l'interface utilisateur, pourquoi voudriez-vous savoir s'il launchmissiesexiste à moins que vous ne vouliez lancer des missiles? Mieux vaut l'exécuter et agir sur le statut de sortie / exceptions
MestreLion
Le problème avec cette méthode est que la sortie est imprimée sur la console. Si vous utilisez des pipes et shell = True, alors l'OSError ne se lève jamais
Nick Humrich
Sur macOS, vous avez également des exécutables stub pour, par exemple, gitque vous ne voulez probablement pas exécuter en aveugle.
Bob Aman
5

Je sais que je suis un peu un nécromancien ici, mais je suis tombé sur cette question et la solution acceptée n'a pas fonctionné pour moi dans tous les cas J'ai pensé qu'il pourrait être utile de soumettre de toute façon. En particulier, la détection du mode "exécutable" et la nécessité de fournir l'extension de fichier. De plus, python3.3 shutil.which(utilise PATHEXT) et python2.4 + distutils.spawn.find_executable(essaie simplement d'ajouter '.exe') ne fonctionnent que dans un sous-ensemble de cas.

J'ai donc écrit une version "super" (basée sur la réponse acceptée et la PATHEXTsuggestion de Suraj). Cette version de whichfait la tâche un peu plus en profondeur, et essaie d'abord une série de techniques en largeur "première phase", puis essaie finalement des recherches plus fines sur l' PATHespace:

import os
import sys
import stat
import tempfile


def is_case_sensitive_filesystem():
    tmphandle, tmppath = tempfile.mkstemp()
    is_insensitive = os.path.exists(tmppath.upper())
    os.close(tmphandle)
    os.remove(tmppath)
    return not is_insensitive

_IS_CASE_SENSITIVE_FILESYSTEM = is_case_sensitive_filesystem()


def which(program, case_sensitive=_IS_CASE_SENSITIVE_FILESYSTEM):
    """ Simulates unix `which` command. Returns absolute path if program found """
    def is_exe(fpath):
        """ Return true if fpath is a file we have access to that is executable """
        accessmode = os.F_OK | os.X_OK
        if os.path.exists(fpath) and os.access(fpath, accessmode) and not os.path.isdir(fpath):
            filemode = os.stat(fpath).st_mode
            ret = bool(filemode & stat.S_IXUSR or filemode & stat.S_IXGRP or filemode & stat.S_IXOTH)
            return ret

    def list_file_exts(directory, search_filename=None, ignore_case=True):
        """ Return list of (filename, extension) tuples which match the search_filename"""
        if ignore_case:
            search_filename = search_filename.lower()
        for root, dirs, files in os.walk(path):
            for f in files:
                filename, extension = os.path.splitext(f)
                if ignore_case:
                    filename = filename.lower()
                if not search_filename or filename == search_filename:
                    yield (filename, extension)
            break

    fpath, fname = os.path.split(program)

    # is a path: try direct program path
    if fpath:
        if is_exe(program):
            return program
    elif "win" in sys.platform:
        # isnt a path: try fname in current directory on windows
        if is_exe(fname):
            return program

    paths = [path.strip('"') for path in os.environ.get("PATH", "").split(os.pathsep)]
    exe_exts = [ext for ext in os.environ.get("PATHEXT", "").split(os.pathsep)]
    if not case_sensitive:
        exe_exts = map(str.lower, exe_exts)

    # try append program path per directory
    for path in paths:
        exe_file = os.path.join(path, program)
        if is_exe(exe_file):
            return exe_file

    # try with known executable extensions per program path per directory
    for path in paths:
        filepath = os.path.join(path, program)
        for extension in exe_exts:
            exe_file = filepath+extension
            if is_exe(exe_file):
                return exe_file

    # try search program name with "soft" extension search
    if len(os.path.splitext(fname)[1]) == 0:
        for path in paths:
            file_exts = list_file_exts(path, fname, not case_sensitive)
            for file_ext in file_exts:
                filename = "".join(file_ext)
                exe_file = os.path.join(path, filename)
                if is_exe(exe_file):
                    return exe_file

    return None

L'utilisation ressemble à ceci:

>>> which.which("meld")
'C:\\Program Files (x86)\\Meld\\meld\\meld.exe'

La solution acceptée n'a pas fonctionné pour moi dans ce cas, car il les fichiers étaient comme meld.1, meld.ico, meld.doap, etc également dans le répertoire, dont un a été renvoyé à la place (probablement depuis lexicographique en premier) parce que le test exécutable dans la réponse acceptée était incomplète et donner faux positifs.

Preet Kukreti
la source
2

J'ai trouvé quelque chose dans StackOverflow qui a résolu le problème pour moi. Cela fonctionne à condition que l'exécutable dispose d'une option (comme --help ou --version) qui génère quelque chose et renvoie un état de sortie de zéro. Voir Supprimer la sortie dans les appels Python aux exécutables - le "résultat" à la fin de l'extrait de code dans cette réponse sera zéro si l'exécutable est dans le chemin, sinon il est très probablement égal à 1.

Somesh
la source
2

Cela semble assez simple et fonctionne à la fois en python 2 et 3

try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')
jaap
la source
Désolé Jaap, mais cette solution ne fonctionne que lorsque l'exécutable n'appelle pas un code de sortie 1 s'il est appelé incorrectement. Ainsi, par exemple, cela fonctionnera pour "dir" et "ls", mais si vous exécutez contre quelque chose qui nécessite une configuration, il se cassera même si l'exécutable est là.
Spedge
1
Qu'entendez-vous exactement par «exiger une configuration»? En soi, «qui» n'exécute rien, mais vérifie simplement le CHEMIN pour l'existence d'un exécutable de ce nom (man which).
jaap
1
Ohh, vous utilisez donc "qui" pour trouver l'exécutable. Donc, cela ne fonctionne que pour Linux / Unix?
Spedge
1
Utilisez command -v executableou type executablepour être universel. Il y a des cas où qui sur Mac ne renvoie pas les résultats attendus.
RJ
1

Une question importante est " Pourquoi avez-vous besoin de tester si un exécutable existe?" Peut-être pas? ;-)

Récemment, j'avais besoin de cette fonctionnalité pour lancer la visionneuse pour le fichier PNG. Je voulais répéter sur certains téléspectateurs prédéfinis et exécuter le premier qui existe. Heureusement, je suis tombé sur os.startfile. C'est beaucoup mieux! Simple, portable et utilise la visionneuse par défaut sur le système:

>>> os.startfile('yourfile.png')

Mise à jour: j'avais tort d' os.startfileêtre portable ... C'est Windows uniquement. Sur Mac, vous devez exécuter la opencommande. Et xdg_opensur Unix. Il y a un problème Python sur l'ajout de la prise en charge Mac et Unix pour os.startfile.

cendre
la source
1

Vous pouvez essayer la bibliothèque externe appelée "sh" ( http://amoffat.github.io/sh/ ).

import sh
print sh.which('ls')  # prints '/bin/ls' depending on your setup
print sh.which('xxx') # prints None
jung rhew
la source
1

Ajout du support Windows

def which(program):
    path_ext = [""];
    ext_list = None

    if sys.platform == "win32":
        ext_list = [ext.lower() for ext in os.environ["PATHEXT"].split(";")]

    def is_exe(fpath):
        exe = os.path.isfile(fpath) and os.access(fpath, os.X_OK)
        # search for executable under windows
        if not exe:
            if ext_list:
                for ext in ext_list:
                    exe_path = "%s%s" % (fpath,ext)
                    if os.path.isfile(exe_path) and os.access(exe_path, os.X_OK):
                        path_ext[0] = ext
                        return True
                return False
        return exe

    fpath, fname = os.path.split(program)

    if fpath:
        if is_exe(program):
            return "%s%s" % (program, path_ext[0])
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return "%s%s" % (exe_file, path_ext[0])
    return None
wukong
la source
0

vous pouvez savoir si un fichier existe avec le module os. un exécutable en particulier semble tout à fait impraticable étant donné que beaucoup de choses sont exécutables sur nix qui ne sont pas sur windows et vice versa.

Dustin Getz
la source
0

Il semblerait que le choix évident soit "qui", en analysant les résultats via popen, mais vous pouvez le simuler autrement en utilisant la classe os. En pseudopython, cela ressemblerait à ceci:

for each element r in path:
    for each file f in directory p:
        if f is executable:
           return True
Charlie Martin
la source
Je serais prudent de lancer une commande "which" en utilisant os.exec ou quelque chose comme ça. Non seulement elle est souvent lente (si les performances sont un problème quelconque), mais si vous utilisez une variable dans le cadre de votre chaîne d'exécution, la sécurité devient un problème. Quelqu'un pourrait se faufiler dans un "rm -rf /".
Parappa
1
Laquelle, puisque nous utiliserions la fonction os.popen pour exécuter une commande créée par le programme, ne s'applique pas réellement, non?
Charlie Martin
2
Merci, mais je ne sais pas si «qui» existe sur Windows et autres. Je voulais essentiellement savoir si quelque chose de fantaisiste existe dans la
bibliothèque
Dans les installations Windows standard, il n'y a toujours pas de whichcommande; il existe une version UnxUtils, mais vous devez connaître / spécifier l'extension, sinon le programme ne sera pas trouvé.
Tobias
0

Donc, fondamentalement, vous voulez trouver un fichier dans un système de fichiers monté (pas nécessairement dans les répertoires PATH uniquement) et vérifier s'il est exécutable. Cela se traduit par le plan suivant:

  • énumérer tous les fichiers dans les systèmes de fichiers montés localement
  • faire correspondre les résultats avec le modèle de nom
  • pour chaque fichier trouvé, vérifiez s'il est exécutable

Je dirais que faire cela de manière portable nécessitera beaucoup de puissance de calcul et de temps. Est-ce vraiment ce dont vous avez besoin?

zgoda
la source
0

Il existe un script which.py dans une distribution Python standard (par exemple sous Windows '\PythonXX\Tools\Scripts\which.py').

EDIT: which.pydépend lsdonc il n'est pas multi-plateforme.

jfs
la source
0

Aucun des exemples précédents ne fonctionne sur toutes les plateformes. Habituellement, ils ne fonctionnent pas sous Windows car vous pouvez exécuter sans l'extension de fichier et que vous pouvez enregistrer une nouvelle extension. Par exemple sous Windows, si python est bien installé, il suffit d'exécuter 'file.py' et cela fonctionnera.

La seule solution valide et portable que j'avais était d'exécuter la commande et de voir le code d'erreur. Tout exécutable décent devrait avoir un ensemble de paramètres d'appel qui ne feront rien.

Sorin
la source
-3

Utilisation de la bibliothèque de tissus python:

from fabric.api import *

def test_cli_exists():
    """
    Make sure executable exists on the system path.
    """
    with settings(warn_only=True):
        which = local('which command', capture=True)

    if not which:
        print "command does not exist"

    assert which
frodopwns
la source
2
C'est une très mauvaise suggestion. Vous faites littéralement dépendre le programme de la bibliothèque d' exécution à distance pour générer un programme local (ce que Python stdlib peut faire facilement), et en plus, vous dépendez de ce which(1)qui n'est pas présent sur tous les systèmes.
Michał Górny