¿Prueba si el ejecutable existe en Python?

297

En Python, ¿hay una forma portátil y simple de probar si existe un programa ejecutable?

Por simple me refiero a algo como el whichcomando que sería perfecto. No quiero buscar PATH manualmente o algo que implique intentar ejecutarlo con Popen& al y ver si falla (eso es lo que estoy haciendo ahora, pero imagínelo launchmissiles)

Piotr Lesnicki
fuente
44
¿Qué hay de malo en buscar la variable de entorno PATH? ¿Qué crees que hace el comando 'qué' UNIX?
Jay
1
¿El script which.py ​​de stdlib es simple?
jfs
@JF: el script which.py ​​incl. con Python depende de 'ls' y algunos de los otros comentarios indican que Piotr estaba buscando una respuesta multiplataforma.
Jay
@ Jay: Gracias por el comentario. Tengo coreutils instalado en Windows, así que no noté que which.py ​​es específico de Unix.
jfs
También está whichel módulo de terceros: code.activestate.com/pypm/which
Sridhar Ratnakumar

Respuestas:

321

La forma más fácil que se me ocurre:

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

Editar : Ejemplo de código actualizado para incluir la lógica para el manejo de casos donde el argumento proporcionado ya es una ruta completa al ejecutable, es decir, "which / bin / ls". Esto imita el comportamiento del comando UNIX 'which'.

Editar : actualizado para usar os.path.isfile () en lugar de os.path.exists () por comentarios.

Editar : path.strip('"')parece que es algo incorrecto hacer aquí. Ni Windows ni POSIX parecen alentar los elementos de RUTA citados.

Arrendajo
fuente
Gracias Jay, acepto tu respuesta, aunque para mí responde negativamente a mi pregunta. No existe tal función en las bibliotecas, solo tengo que escribirla (admito que mi formulación no fue lo suficientemente clara en el hecho de que sé lo que hace).
Piotr Lesnicki
1
Jay, si completas tu respuesta de acuerdo con la mía (para tener 'w' completa) para que pueda eliminar la mía.
Piotr Lesnicki
2
Para algunos sistemas operativos, es posible que deba agregar la extensión del ejecutable. Por ejemplo, en Ubuntu puedo escribir cuál ("scp") pero en Windows, necesitaba escribir cuál ("scp.exe").
waffleman
13
Sugeriría cambiar "os.path.exists" a "os.path.isfile". De lo contrario, en Unix, esto podría hacer coincidir falsamente un directorio con el conjunto de bits + x. También me parece útil agregar esto a la parte superior de la función: import sys; si sys.platform == "win32" y no program.endswith (". exe"): programa + = ".exe". De esta manera, en Windows puede hacer referencia a "calc" o "calc.exe", tal como lo haría en una ventana de cmd.
Kevin Ivarsen
1
@KevinIvarsen Una mejor opción sería recorrer los valores de la PATHEXTvariable env porque commandes tan válida como command.comlo es scriptvsscript.bat
Lekensteyn
325

Sé que esta es una pregunta antigua, pero puedes usarla distutils.spawn.find_executable. Esto se ha documentado desde Python 2.4 y existe desde Python 1.6.

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

Además, Python 3.3 ahora ofrece shutil.which().

Nathan Binkert
fuente
77
Activado win32, la distutils.spawn.find_executableimplementación solo busca en .exelugar de usar la lista de extensiones para buscar el conjunto %PATHEXT%. Eso no es genial, pero podría funcionar para todos los casos que alguien necesita.
rakslice
77
ejemplo de uso:from distutils import spawn php_path = spawn.find_executable("php")
codefreak
66
Aparentemente distutils.spawnno está disponible de manera confiable: con la instalación de mi sistema (/ usr / bin / python) de Python 2.7.6 en OS X 10.10, obtengo: AttributeError: 'module' object has no attribute 'spawn'aunque extrañamente funciona en la misma máquina con la misma versión de Python, pero desde una instalación virtualenv.
Josh Kupershmidt
8
@JoshKupershmidt, asegúrese de import distutils.spawnseguir o seguir la from distutils import spawnsintaxis en lugar de simplemente import distutils. De lo contrario, es posible que no sea accesible y obtendrá lo anterior AttributeErrorincluso si está allí.
John St. John
39

Para python 3.2 y anteriores:

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

Esta es una respuesta de Jay's Answer , también aquí como una función 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')

O, por último, sangrado como una función:

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)
    )

Para python 3.3 y posterior:

import shutil

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

Como una frase de Jan-Philip Gehrcke Respuesta :

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

Como definición:

def cmd_exists(cmd):
    return shutil.which(cmd) is not None
ThorSummoner
fuente
1
la versión "sangrado como una función" utiliza la variable xdonde debería estarcmd
0x89
también debes agregar una prueba para ver si os.path.join(path, cmd)es un archivo, ¿no? Después de todo, los directorios también pueden tener el bit ejecutable establecido ...
MestreLion
@MestreLion Eso suena como un posible caso, ¿le importaría confirmar este comportamiento y actualizar esta respuesta? Me complace cambiar esta publicación a una wiki comunitaria si eso ayuda.
ThorSummoner
1
@ThorSummoner: Lo he confirmado y, de hecho, requiere la prueba del archivo. Una prueba 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
Agregar un simple and os.path.isfile(...)a los lugares apropiados es suficiente para arreglar eso
MestreLion
19

Solo recuerde especificar la extensión del archivo en Windows. De lo contrario, tienes que escribir un libro muy complicadois_exePATHEXT variable para Windows utilizando la variable de entorno. Es posible que solo desee utilizar FindPath .

OTOH, ¿por qué te molestas en buscar el ejecutable? El sistema operativo lo hará por usted como parte depopen llamada y generará una excepción si no se encuentra el ejecutable. Todo lo que necesita hacer es detectar la excepción correcta para un sistema operativo determinado. Tenga en cuenta que en Windows, subprocess.Popen(exe, shell=True)fallará en silencio si exeno se encuentra.


Incorporando PATHEXTa la implementación anterior de which(en la respuesta 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
fuente
1
Se solucionó un error en la respuesta aceptada, cree que esta respuesta debería estar en la parte superior.
NiTe Luo
El uso inteligente de yieldin ext_candidates, me dio una mejor comprensión de cómo funciona esa palabra clave
Grant Humphries
15

Para plataformas * nix (Linux y OS X)

Esto parece estar funcionando para mí:

Editado para trabajar en Linux, gracias a Mestreion

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

Lo que estamos haciendo aquí es usar el comando incorporado typey verificar el código de salida. Si no existe dicho comando, typesaldrá con 1 (o un código de estado distinto de cero de todos modos)

La parte sobre stdout y stderr es solo para silenciar la salida del typecomando, ya que solo estamos interesados ​​en el código de estado de salida.

Ejemplo de uso:

>>> 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
fuente
2
¿Estás seguro de que esto funciona? Es un enfoque muy agradable, pero typees un shell integrado, no un archivo ejecutable, por lo que subprocess.call()falla aquí.
MestreLion
1
¿Lo has probado o solo estás teorizando? Funciona en mi mac de todos modos.
Hasen
Lo he probado en Ubuntu 12.04, arroja OSError: [Errno 2] No such file or directory. Tal vez en Mac typees un comando real
MestreLion
2
Después de MUCHAS pruebas, he encontrado cómo solucionarlo: agregar shell=Truey reemplazar ["type", cmd]para"type " + cmd
MestreLion
44
Atención: asegúrese de que la variable "cmd" contenga datos válidos. Si proviene de una fuente externa, un chico malo podría darte "ls; rm -rf /". Creo que la solución en python (sin subproceso) es mucho mejor. Siguiente punto: si llama a este método con frecuencia, la solución del subproceso es mucho más lenta, ya que es necesario generar muchos procesos.
guettli
7

Consulte el módulo os.path para ver algunas funciones útiles sobre nombres de ruta. Para verificar si un archivo existente es ejecutable, use os.access (ruta, modo) , con el modo os.X_OK.

os.X_OK

Valor para incluir en el parámetro de modo de acceso () para determinar si se puede ejecutar la ruta.

EDITAR: a laswhich() implementaciones sugeridas les falta una pista: usar os.path.join()para construir nombres de archivo completos.

gimel
fuente
Gracias, Gimel, así que básicamente tengo mi respuesta: no existe tal función, debo hacerlo manualmente.
Piotr Lesnicki
No utilices os.access. La función de acceso está diseñada para programas suid.
Changming Sun
6

Sobre la base de que es más fácil pedir perdón que permiso , solo trataría de usarlo y detectar el error (OSError en este caso, verifiqué si el archivo no existe y el archivo no es ejecutable y ambos dan OSError).

Ayuda si el ejecutable tiene algo así como una --versionbandera que es un no-op rápido.

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

Esta no es una solución general, pero será la forma más fácil para muchos casos de uso, aquellos en los que el código debe buscar un único ejecutable bien conocido.

Hamish Downer
fuente
3
¡Es demasiado peligroso incluso llamar --versiona un programa llamado launchmissiles!
xApple
1
+1, me gusta este enfoque. EAFP es una regla de oro de Python. Excepto quizás para configurar la interfaz de usuario, ¿por qué querrías saber si launchmissiesexiste a menos que quieras lanzar misiles? Es mejor ejecutarlo y actuar sobre el estado / excepciones de salida
MestreLion
El problema con este método es que la salida se imprime en la consola. Si usa tuberías y shell = True, entonces el OSError nunca se eleva
Nick Humrich
En macOS también tiene ejecutables de código auxiliar, por ejemplo, gitque probablemente no desee ejecutar a ciegas.
Bob Aman
5

Sé que estoy siendo un poco nigromante aquí, pero me topé con esta pregunta y la solución aceptada no funcionó para mí en todos los casos. Pensé que podría ser útil presentarla de todos modos. En particular, la detección del modo "ejecutable" y el requisito de suministrar la extensión del archivo. Además, tanto python3.3 shutil.which(usa PATHEXT) como python2.4 + distutils.spawn.find_executable(solo intenta agregar '.exe') solo funcionan en un subconjunto de casos.

Entonces escribí una versión "super" (basada en la respuesta aceptada y la PATHEXTsugerencia de Suraj). Esta versión de whichrealiza la tarea un poco más a fondo, e intenta primero una serie de técnicas de "amplitud de amplitud" primero, y finalmente intenta realizar búsquedas más detalladas en el PATHespacio:

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

El uso se ve así:

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

La solución aceptada no funcionó para mí en este caso, ya que no había archivos como meld.1, meld.ico, meld.doap, etc también en el directorio, uno de los cuales fueron devueltos lugar (presumiblemente desde lexicográfico primero) porque la prueba ejecutable en la respuesta aceptada era incompleta y dando falsos positivos.

Preet Kukreti
fuente
2

Encontré algo en StackOverflow que me resolvió el problema. Esto funciona siempre que el ejecutable tenga una opción (como --help o --version) que genera algo y devuelve un estado de salida de cero. Consulte Suprimir salida en llamadas de Python a ejecutables : el "resultado" al final del fragmento de código en esta respuesta será cero si el ejecutable está en ruta, de lo contrario es más probable que sea 1.

Somesh
fuente
2

Esto parece bastante simple y funciona tanto en Python 2 como en 3

try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')
jaap
fuente
Lo siento, Jaap, pero esta solución solo funciona cuando el ejecutable no llama a un código de salida 1 si se llama incorrectamente. Entonces, por ejemplo, funcionará para "dir" y "ls", pero si ejecuta contra algo que requiere configuración, se romperá aunque el ejecutable esté allí.
Spedge
1
¿Qué quiere decir exactamente con "requerir configuración"? Por sí mismo, 'which' en realidad no ejecuta nada, solo verifica la RUTA para ver si existe un ejecutable con este nombre (man which).
jaap
1
Ohh, entonces estás usando "which" para encontrar el ejecutable. ¿Entonces esto solo funciona para Linux / Unix?
Spedge
1
Uso command -v executableo type executablepara ser universal. Hay casos en los que en Mac no devuelve los resultados esperados.
RJ
1

Una pregunta importante es " ¿Por qué necesita probar si existe el ejecutable?" Tal vez no? ;-)

Recientemente necesitaba esta funcionalidad para iniciar el visor de archivos PNG. Quería iterar sobre algunos visores predefinidos y ejecutar el primero que existe. Afortunadamente, me encontré os.startfile. ¡Es mucho mejor! Simple, portátil y utiliza el visor predeterminado en el sistema:

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

Actualización: Estaba equivocado acerca de os.startfileser portátil ... Es solo Windows. En Mac tienes que ejecutar el opencomando. Y xdg_openen Unix. Hay un problema con Python al agregar soporte para Mac y Unix os.startfile.

ceniza
fuente
1

Puede probar la biblioteca externa llamada "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
fuente
1

Soporte de Windows agregado

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
fuente
0

puede saber si existe un archivo con el módulo os. un ejecutable en particular parece bastante inportable teniendo en cuenta que muchas cosas son ejecutables en nix que no están en Windows y viceversa.

Dustin Getz
fuente
0

Parece que la opción obvia es "cuál", analizando los resultados a través de popen, pero de lo contrario podría simularlo utilizando la clase os. En pseudopython, se vería así:

for each element r in path:
    for each file f in directory p:
        if f is executable:
           return True
Charlie Martin
fuente
Tendría cuidado al ejecutar un comando "which" usando os.exec o algo así. No solo suele ser lento (si el rendimiento es un problema), sino que si está utilizando una variable como parte de su cadena de ejecución, la seguridad se convierte en una preocupación. Alguien podría colarse en un "rm -rf /".
Parappa
1
¿Cuál, ya que estaríamos usando la función os.popen para ejecutar un comando creado por el programa, en realidad no se aplica, no?
Charlie Martin
2
Gracias, pero no estoy seguro de si 'cuál' existe en Windows y Me gusta. Esencialmente quería saber si existe algo elegante en la
biblioteca
En las instalaciones estándar de Windows, todavía no hay whichcomando; hay una versión de UnxUtils, pero debe conocer / especificar la extensión, de lo contrario no se encontrará el programa.
Tobias
0

Entonces, básicamente, desea encontrar un archivo en el sistema de archivos montado (no necesariamente solo en los directorios PATH) y verificar si es ejecutable. Esto se traduce en el siguiente plan:

  • enumerar todos los archivos en sistemas de archivos montados localmente
  • igualar los resultados con el patrón de nombre
  • para cada archivo encontrado verifique si es ejecutable

Yo diría que hacer esto de forma portátil requerirá mucha potencia y tiempo de computación. ¿Es realmente lo que necesitas?

zgoda
fuente
0

Hay un script which.py en una distribución estándar de Python (por ejemplo, en Windows '\PythonXX\Tools\Scripts\which.py').

EDITAR: which.pydepende de lspor lo tanto, no es multiplataforma.

jfs
fuente
0

Ninguno de los ejemplos anteriores funciona en todas las plataformas. Por lo general, no funcionan en Windows porque puede ejecutar sin la extensión de archivo y puede registrar una nueva extensión. Por ejemplo, en Windows, si Python está bien instalado, es suficiente para ejecutar 'file.py' y funcionará.

La única solución válida y portátil que tenía era ejecutar el comando y ver el código de error. Cualquier ejecutable decente debe tener un conjunto de parámetros de llamada que no harán nada.

Sorin
fuente
-3

Usando la biblioteca de tela 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
fuente
2
Esta es una muy mala sugerencia. Literalmente, está haciendo que el programa dependa de la biblioteca de ejecución remota para generar un programa local (que Python stdlib puede hacer fácilmente), y además, depende de which(1)qué no está presente en todos los sistemas.
Michał Górny