Test se il file eseguibile esiste in Python?


297

In Python, esiste un modo portatile e semplice per verificare se esiste un programma eseguibile?

Con semplice intendo qualcosa come il whichcomando che sarebbe semplicemente perfetto. Non voglio cercare manualmente il PERCORSO o qualcosa che coinvolge il tentativo di eseguirlo con Popen& al e vedere se fallisce (è quello che sto facendo ora, ma immagina che sia launchmissiles)


4
Cosa c'è di sbagliato nella ricerca della variabile d'ambiente PATH? Cosa pensi che faccia il comando 'quale' UNIX?
Jay,

1
Lo script which.py ​​di stdlib è un modo semplice?
jfs

@JF - lo script who.py incl. con Python dipende da 'ls' e alcuni degli altri commenti indicano che Piotr stava cercando una risposta multipiattaforma.
Jay,

@Jay: grazie per il commento. Ho coreutils installato su Windows, quindi non ho notato che what.py è specifico per Unix.
jfs il

C'è anche whichil modulo di terze parti: code.activestate.com/pypm/which
Sridhar Ratnakumar,

Risposte:


321

Il modo più semplice che mi viene in mente:

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

Modifica : esempio di codice aggiornato per includere la logica per la gestione del caso in cui l'argomento fornito è già un percorso completo dell'eseguibile, ovvero "quale / bin / ls". Questo imita il comportamento del comando 'quale' UNIX.

Modifica : aggiornato per utilizzare os.path.isfile () anziché os.path.exists () per commenti.

Modifica : path.strip('"')sembra la cosa sbagliata da fare qui. Né Windows né POSIX sembrano incoraggiare gli elementi PATH citati.


Grazie Jay, accetto la tua risposta, anche se per me risponde alla mia domanda in negativo. Non esiste una tale funzione nelle librerie, devo solo scriverla (ammetto che la mia formulazione non era abbastanza chiara nel fatto che so cosa fa).
Piotr Lesnicki,

1
Jay, se completi la tua risposta secondo la mia (per avere una 'w' completa) così posso rimuovere la mia.
Piotr Lesnicki,

2
Per alcuni sistemi operativi potrebbe essere necessario aggiungere l'estensione dell'eseguibile. Ad esempio, su Ubuntu posso scrivere quale ("scp") ma su Windows, dovevo scrivere quale ("scp.exe").
Waffleman,

13
Suggerirei di cambiare "os.path.exists" in "os.path.isfile". Altrimenti in Unix questo potrebbe erroneamente corrispondere a una directory con il set di bit + x. Trovo anche utile aggiungere questo all'inizio della funzione: import sys; se sys.platform == "win32" e non program.endswith (". exe"): programma + = ".exe". In questo modo in Windows puoi fare riferimento a "calc" o "calc.exe", proprio come potresti fare in una finestra cmd.
Kevin Ivarsen,

1
@KevinIvarsen Un'opzione migliore sarebbe scorrere i valori di PATHEXTenv var perché commandè valido come command.comlo è scriptvsscript.bat
Lekensteyn

325

So che questa è una domanda antica, ma puoi usarla distutils.spawn.find_executable. Questo è stato documentato da Python 2.4 ed esiste da Python 1.6.

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

Inoltre, Python 3.3 ora offre shutil.which().


7
win32, l' distutils.spawn.find_executableimplementazione cerca solo .exeanziché utilizzare l'elenco di estensioni per cercare set in %PATHEXT%. Non è eccezionale, ma potrebbe funzionare per tutti i casi di cui qualcuno ha bisogno.
Rakslice,

7
esempio di utilizzo:from distutils import spawn php_path = spawn.find_executable("php")
codefreak

6
Apparentemente distutils.spawnnon è disponibile in modo affidabile: con la mia installazione di sistema (/ usr / bin / python) di Python 2.7.6 su OS X 10.10, ottengo AttributeError: 'module' object has no attribute 'spawn':, sebbene stranamente funzioni sulla stessa macchina con la stessa versione di Python, ma da un'installazione virtualenv.
Josh Kupershmidt,

8
@JoshKupershmidt, assicurati di import distutils.spawn, o segui la from distutils import spawnsintassi piuttosto che semplicemente import distutils. Altrimenti potrebbe non essere accessibile e otterrai quanto sopra AttributeErroranche se è lì.
John St. John,


39

Per python 3.2 e precedenti:

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

Questa è una riga della risposta di Jay , anche qui come funzione 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 infine, rientrato in funzione:

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

Per python 3.3 e versioni successive:

import shutil

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

Come interlocutore di Jan-Philip Gehrcke Risposta :

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

Come def:

def cmd_exists(cmd):
    return shutil.which(cmd) is not None

1
la versione "indentata come una funzione" utilizza la variabile xdove dovrebbe esserecmd
0x89

devi anche aggiungere un test per vedere se os.path.join(path, cmd)è un file, no? Dopotutto, le directory possono anche avere il bit eseguibile impostato ...
MestreLion

@MestreLion Sembra un caso possibile, ti dispiacerebbe confermare questo comportamento e aggiornare questa risposta? Sono felice di cambiare questo post in un wiki della community se questo aiuta.
ThorSummoner,

1
@ThorSummoner: l'ho confermato e richiede davvero il test per il file. Un semplice test: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
L'aggiunta di un semplice and os.path.isfile(...)nei luoghi appropriati è sufficiente per risolvere il problema
MestreLion

19

Ricorda solo di specificare l'estensione del file su Windows. Altrimenti, devi scrivere molto complicato is_exeper Windows usando PATHEXTla variabile d'ambiente. Potresti semplicemente voler usare FindPath .

OTOH, perché ti stai nemmeno preoccupando di cercare l'eseguibile? Il sistema operativo lo farà per te come parte della popenchiamata e genererà un'eccezione se l'eseguibile non viene trovato. Tutto quello che devi fare è prendere l'eccezione corretta per un determinato sistema operativo. Nota che su Windows subprocess.Popen(exe, shell=True)non funzionerà in modo silenzioso se exenon viene trovato.


Integrazione PATHEXTnella precedente implementazione di which(nella risposta di 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

1
È stato risolto un bug nella risposta accettata, ma invece questa risposta dovrebbe essere in cima.
NiTe Luo,

un uso intelligente di yieldin ext_candidates, mi ha permesso di capire meglio come funziona quella parola chiave
Grant Humphries,

15

Per piattaforme * nix (Linux e OS X)

Questo sembra funzionare per me:

Modificato per funzionare su Linux, grazie a Mestreion

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

Quello che stiamo facendo qui è usare il comando incorporato typee controllare il codice di uscita. Se non esiste tale comando, typeuscirà con 1 (o comunque con un codice di stato diverso da zero).

La parte su stdout e stderr è solo per mettere a tacere l'output di type comando, poiché siamo interessati solo al codice dello stato di uscita.

Esempio di utilizzo:

>>> cmd_exists("jsmin")
True
>>> cmd_exists("cssmin")
False
>>> cmd_exists("ls")
True
>>> cmd_exists("dir")
False
>>> cmd_exists("node")
True
>>> cmd_exists("steam")
False

2
Sei sicuro che funzioni ? È un approccio molto carino, ma typeè un built-in della shell, non un file eseguibile, quindi subprocess.call()qui fallisce.
MestreLion,

1
Ci hai provato o stai solo teorizzando? Funziona comunque sul mio mac.
hasen

L'ho provato in Ubuntu 12.04, genera OSError: [Errno 2] No such file or directory. Forse su Mac typeè un vero comando
MestreLion,

2
Dopo MOLTI test, ho scoperto come risolvere: aggiungere shell=Truee sostituire ["type", cmd]per"type " + cmd
MestreLion,

4
Attenzione: assicurarsi che la variabile "cmd" contenga dati validi. Se proviene da una fonte esterna, un cattivo potrebbe darti "ls; rm -rf /". Penso che la soluzione in-python (senza sottoprocesso) sia molto migliore. Punto successivo: Se si chiama questo metodo spesso, la soluzione di sottoprocesso è molto più lenta, poiché devono essere generati molti processi.
Guettli,

7

Vedi il modulo os.path per alcune utili funzioni sui nomi dei percorsi. Per verificare se un file esistente è eseguibile, utilizzare os.access (percorso, modalità) , con la modalità os.X_OK.

os.X_OK

Valore da includere nel parametro mode di access () per determinare se il percorso può essere eseguito.

EDIT: le which()implementazioni suggerite mancano di un indizio - usando os.path.join()per costruire nomi di file completi.


Grazie, gimel, quindi sostanzialmente ho la mia risposta: nessuna di queste funzioni esiste, devo farlo manualmente.
Piotr Lesnicki,

Non usare os.access. la funzione di accesso è progettata per i programmi di aiuto.
Changming Sun,

6

Sulla base del fatto che è più facile chiedere perdono che permesso , proverei semplicemente a usarlo e catturare l'errore (OSError in questo caso - Ho verificato che il file non esiste e il file non sia eseguibile ed entrambi danno OSError).

Aiuta se l'eseguibile ha qualcosa di simile a una --versionbandiera che è una rapida no-op.

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

Questa non è una soluzione generale, ma sarà il modo più semplice per molti casi d'uso - quelli in cui il codice deve cercare un singolo eseguibile ben noto.


3
È troppo pericoloso persino invocare --versionun programma chiamato launchmissiles!
xApple

1
+1, mi piace questo approccio. EAFP è una regola d'oro di Python. Tranne forse per l'impostazione dell'interfaccia utente, perché vorresti sapere se launchmissiesesiste se non vuoi lanciare missili? Meglio eseguirlo e agire in base allo stato / eccezioni di uscita
MestreLion,

Il problema con questo metodo è che l'output viene stampato sulla console. Se usi pipe e shell = True, l'OSError non viene mai generato
Nick Humrich,

Su macOS hai anche eseguibili stub per esempio gitche probabilmente non vuoi correre alla cieca.
Bob Aman,

5

So di essere un po 'un negromante qui, ma mi sono imbattuto in questa domanda e la soluzione accettata non ha funzionato per me in tutti i casi Ho pensato che potesse essere utile inviare comunque. In particolare, il rilevamento della modalità "eseguibile" e il requisito di fornire l'estensione del file. Inoltre, sia python3.3 shutil.which(usa PATHEXT) sia python2.4 + distutils.spawn.find_executable(prova solo ad aggiungere'.exe' ) funzionano solo in un sottoinsieme di casi.

Quindi ho scritto una versione "super" (basata sulla risposta accettata e sul PATHEXTsuggerimento di Suraj). Questa versione di whichfa un po 'più a fondo il compito, e prova prima una serie di tecniche "larghe fasi" prima di tutto, e alla fine prova ricerche più approfondite nello PATHspazio:

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'utilizzo è simile al seguente:

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

La soluzione accettata non ha funzionato per me in questo caso, in quanto vi erano file come meld.1, meld.ico, meld.doap, ecc anche nella directory, uno dei quali sono stati restituiti al posto (presumibilmente dal lessicografico prima) perché il test eseguibile nella risposta accettata era incompleta e dando falsi positivi.



2

Ho trovato qualcosa in StackOverflow che ha risolto il problema per me. Funziona a condizione che l'eseguibile abbia un'opzione (come --help o --version) che genera qualcosa e restituisce uno stato di uscita pari a zero. Vedi Sopprimere l'output nelle chiamate Python agli eseguibili : il "risultato" alla fine dello snippet di codice in questa risposta sarà zero se l'eseguibile è nel percorso, altrimenti è più probabile che sia 1.


2

Sembra abbastanza semplice e funziona sia in Python 2 che 3

try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')

Scusa Jaap, ma questa soluzione funziona solo quando l'eseguibile non chiama un codice di uscita 1 se viene chiamato in modo errato. Quindi, per esempio, funzionerà per "dir" e "ls", ma se esegui qualcosa che richiede la configurazione, si romperà anche se l'eseguibile è lì.
Spedge il

1
Cosa intendi esattamente con "richiedi configurazione"? Di per sé 'quale' in realtà non esegue nulla ma controlla semplicemente il PERCORSO per l'esistenza di un eseguibile con questo nome (uomo che).
Jaap

1
Ohh, quindi stai usando "quale" per trovare l'eseguibile. Quindi questo funziona solo per Linux / Unix?
Spedge il

1
Usa command -v executableo type executableper essere universale. Ci sono casi in cui su Mac non vengono restituiti i risultati previsti.
RJ

1

Una domanda importante è " Perché è necessario verificare se esiste un eseguibile?" Forse no? ;-)

Di recente ho avuto bisogno di questa funzionalità per avviare il visualizzatore per il file PNG. Volevo scorrere su alcuni visualizzatori predefiniti ed eseguire il primo esistente. Fortunatamente, mi sono imbattuto os.startfile. È molto meglio! Semplice, portatile e utilizza il visualizzatore predefinito sul sistema:

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

Aggiornamento: mi sbagliavo os.startfilesull'essere portatile ... È solo Windows. Su Mac devi eseguire il opencomando. E xdg_opensu Unix. C'è un problema con Python quando si aggiunge il supporto per Mac e Unix os.startfile.



1

Aggiunto supporto per 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

0

puoi sapere se esiste un file con il modulo os. un eseguibile in particolare sembra abbastanza insostituibile considerando che su nix sono eseguibili molte cose che non sono su Windows e viceversa.


0

Sembrerebbe che la scelta ovvia sia "quale", analizzando i risultati via popen, ma potresti simularlo altrimenti usando la classe os. In pseudopython, sarebbe simile a questo:

for each element r in path:
    for each file f in directory p:
        if f is executable:
           return True

Starei attento a eseguire un comando "which" usando os.exec o qualcosa del genere. Non solo è spesso lento (se le prestazioni sono un tipo di problema), ma se si utilizza una variabile come parte della stringa exec, la sicurezza diventa un problema. Qualcuno potrebbe intrufolarsi in un "rm -rf /".
Parappa,

1
Quale, poiché avremmo usato la funzione os.popen per eseguire un comando creato dal programma, in realtà non si applica, no?
Charlie Martin,

2
Grazie, ma non sono sicuro se "quale" esiste su Windows e simili. Volevo essenzialmente sapere se esiste qualcosa di fantasioso nella lib standard
Piotr Lesnicki

Nelle installazioni standard di Windows, non esiste ancora alcun whichcomando; esiste una versione di UnxUtils, ma è necessario conoscere / specificare l'estensione, altrimenti il ​​programma non verrà trovato.
Tobias,

0

Quindi sostanzialmente vuoi trovare un file nel filesystem montato (non necessariamente solo nelle directory PATH) e controllare se è eseguibile. Questo si traduce nel seguente piano:

  • enumera tutti i file nei filesystem montati localmente
  • abbina i risultati con il modello di nome
  • per ogni file trovato controlla se è eseguibile

Direi che farlo in modo portatile richiederà molta potenza e tempo di elaborazione. È davvero quello che ti serve?


0

Esiste uno script which.py in una distribuzione Python standard (ad es. Su Windows'\PythonXX\Tools\Scripts\which.py' ).

EDIT: which.pydipende lsquindi non è multipiattaforma.


0

Nessuno degli esempi precedenti funziona su tutte le piattaforme. Di solito non funzionano su Windows perché è possibile eseguire senza l'estensione del file e che è possibile registrare una nuova estensione. Ad esempio su Windows se python è ben installato è sufficiente eseguire 'file.py' e funzionerà.

L'unica soluzione valida e portatile che avevo era di eseguire il comando e vedere il codice di errore. Qualsiasi eseguibile decente dovrebbe avere una serie di parametri di chiamata che non faranno nulla.


-3

Utilizzando la libreria di fabric 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

2
Questo è un pessimo suggerimento. Stai letteralmente facendo dipendere il programma dalla libreria di esecuzione remota per generare un programma locale (cosa che Python stdlib può fare facilmente), e inoltre, sei dipendente da which(1)quale non è presente su tutti i sistemi.
Michał Górny,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.