Sprawdź, czy plik wykonywalny istnieje w Pythonie?


297

Czy w Pythonie istnieje przenośny i prosty sposób sprawdzenia, czy istnieje program wykonywalny?

Przez proste rozumiem coś w rodzaju whichpolecenia, które byłoby po prostu idealne. Nie chcę ręcznie szukać ŚCIEŻKI lub czegoś, co wymaga próby wykonania jej za pomocą Popen& al i sprawdzenia, czy się nie powiedzie (to właśnie robię teraz, ale wyobraź sobie, że to launchmissiles)


4
Co jest złego w przeszukiwaniu zmiennej środowiskowej PATH? Jak myślisz, co robi UNIX „które” polecenie?
Jay

1
Czy skrypt which.py ​​ze stdlib to prosty sposób?
jfs,

@JF - skrypt what.py włącznie. z Pythonem zależy od „ls”, a niektóre inne komentarze wskazują, że Piotr szukał odpowiedzi na wiele platform.
Jay

@Jay: Dzięki za komentarz. Mam Coreutils zainstalowane w systemie Windows, więc nie zauważyłem, że what.py jest specyficzny dla Uniksa.
jfs

Istnieje również whichmoduł innej firmy: code.activestate.com/pypm/which
Sridhar Ratnakumar

Odpowiedzi:


321

Najprostszy sposób, w jaki mogę myśleć:

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

Edycja : Zaktualizowano przykładowy kod, aby zawierał logikę do obsługi przypadku, w którym podany argument jest już pełną ścieżką do pliku wykonywalnego, tj. „What / bin / ls”. To naśladuje zachowanie polecenia UNIX „który”.

Edycja : Zaktualizowano, aby używać komend os.path.isfile () zamiast os.path.exists () w komentarzach.

Edycja : path.strip('"')wydaje się, że to niewłaściwa rzecz do zrobienia tutaj. Ani Windows, ani POSIX nie wydają się zachęcać do cytowanych pozycji ŚCIEŻKA.


Dzięki Jay, akceptuję twoją odpowiedź, chociaż dla mnie odpowiada ona na moje pytanie przecząco. W bibliotekach nie ma takiej funkcji, muszę ją tylko napisać (przyznaję, że moje sformułowanie nie było wystarczająco jasne, ponieważ wiem, co się dzieje).
Piotr Lesnicki,

1
Jay, jeśli wypełnisz swoją odpowiedź zgodnie z moim (aby mieć pełne „w”), abym mógł usunąć moje.
Piotr Lesnicki,

2
W przypadku niektórych systemów operacyjnych może być konieczne dodanie rozszerzenia pliku wykonywalnego. Na przykład na Ubuntu mogę pisać, które („scp”), ale w systemie Windows musiałem pisać, które („scp.exe”).
waffleman,

13
Sugeruję zmianę „os.path.exists” na „os.path.isfile”. W przeciwnym razie w Uniksie może to fałszywie pasować do katalogu z ustawionym bitem + x. Przydaje mi się również dodanie tego na górze funkcji: import sys; jeśli sys.platform == "win32", a nie program.endswith (". exe"): program + = ".exe". W ten sposób w systemie Windows możesz odwoływać się do „calc” lub „calc.exe”, tak jak w oknie cmd.
Kevin Ivarsen

1
@KevinIvarsen Lepszym rozwiązaniem byłoby przelotowego wartościami PATHEXTenv var ponieważ commandjest tak ważna, command.comjak scriptvsscript.bat
Lekensteyn

325

Wiem, że to starożytne pytanie, ale możesz z niego skorzystać distutils.spawn.find_executable. Zostało to udokumentowane od wersji Python 2.4 i istnieje od wersji Python 1.6.

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

Ponadto oferuje teraz Python 3.3 shutil.which().


7
On win32The distutils.spawn.find_executablerealizacja tylko wygląda na .exeraczej niż przy użyciu listę rozszerzeń, aby szukać w zestawie %PATHEXT%. To nie jest świetne, ale może działać we wszystkich przypadkach, których ktoś potrzebuje.
rakslice

7
przykładowe użycie:from distutils import spawn php_path = spawn.find_executable("php")
codefreak

6
Najwyraźniej distutils.spawnnie jest dostępny niezawodnie: z moją instalacją systemu (/ usr / bin / python) Python 2.7.6 na OS X 10.10, otrzymuję: AttributeError: 'module' object has no attribute 'spawn'chociaż dziwnie działa na tej samej maszynie z tą samą wersją Pythona, ale z instalacja virtualenv.
Josh Kupershmidt,

8
@JoshKupershmidt, upewnij się import distutils.spawn, że postępujesz zgodnie ze from distutils import spawnskładnią, a nie tylko import distutils. W przeciwnym razie może nie być dostępny i otrzymasz powyższe, AttributeErrornawet jeśli tam jest.
John St. John,


39

W przypadku python 3.2 i wcześniejszych:

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

To jest jednowierszowa odpowiedź Jaya , również tutaj jako funkcja 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')

Lub wreszcie, wcięte jako funkcja:

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

W przypadku python 3.3 i nowszych:

import shutil

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

Jako jedno-linijka odpowiedzi Jana-Philipa Gehrckego :

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

Z definicji:

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

1
wersja „wcięta jako funkcja” używa zmiennej xtam, gdzie powinna byćcmd
0x89

musisz również dodać test, aby sprawdzić, czy os.path.join(path, cmd)plik jest, prawda? W końcu katalogi mogą mieć również ustawiony bit wykonywalny ...
MestreLion

@MestreLion To brzmi jak możliwy przypadek, czy mógłbyś potwierdzić to zachowanie i zaktualizować tę odpowiedź? Z przyjemnością zmieniam ten post na wiki społeczności, jeśli to pomoże.
ThorSummoner

1
@ThorSummoner: Potwierdziłem to i rzeczywiście wymaga testu pliku. Prosty 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
and os.path.isfile(...)Wystarczy dodać prosty do odpowiednich miejsc, aby to naprawić
MestreLion

19

Pamiętaj tylko, aby określić rozszerzenie pliku w systemie Windows. W przeciwnym razie musisz napisać bardzo skomplikowane is_exedla systemu Windows przy użyciu PATHEXTzmiennej środowiskowej. Możesz po prostu użyć FindPath .

OTOH, dlaczego w ogóle zawracasz sobie głowę szukaniem pliku wykonywalnego? System operacyjny zrobi to za Ciebie w ramach popenwywołania i zgłosi wyjątek, jeśli plik wykonywalny nie zostanie znaleziony. Wszystko, co musisz zrobić, to złapać poprawny wyjątek dla danego systemu operacyjnego. Pamiętaj, że w systemie Windows subprocess.Popen(exe, shell=True)nie powiedzie się po cichu, jeśli exenie zostanie znaleziony.


Włączenie PATHEXTdo powyższej implementacji which(w odpowiedzi Jaya):

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
Naprawiono błąd w zaakceptowanej odpowiedzi, uważam, że ta odpowiedź powinna być na górze.
NiTe Luo,

sprytne użycie yieldin ext_candidates, pozwoliło mi lepiej zrozumieć, jak działa to słowo kluczowe
Grant Humphries

15

Dla platform * nix (Linux i OS X)

To wydaje się działać dla mnie:

Edytowane do pracy w systemie Linux, dzięki Mestreion

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

To, co tu robimy, to użycie wbudowanego polecenia typei sprawdzenie kodu wyjścia. Jeśli nie ma takiego polecenia, typewyjdzie z 1 (lub niezerowym kodem statusu i tak).

Trochę o stdout i stderr jest po prostu wyciszenie wyniku typepolecenia, ponieważ interesuje nas tylko kod statusu wyjścia.

Przykładowe użycie:

>>> 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
Jesteś pewien , że to działa? Jest to bardzo miłe podejście, ale typema wbudowaną powłokę, a nie plik wykonywalny, więc subprocess.call()tutaj zawodzi.
MestreLion

1
Próbowałeś tego, czy tylko teoretyzujesz? W każdym razie działa na moim komputerze Mac.
hasen

Próbowałem w Ubuntu 12.04, rzuca OSError: [Errno 2] No such file or directory. Być może na Macu typejest faktyczne polecenie
MestreLion

2
Po LOT testów, znalazłem jak to naprawić: add shell=Truei wymienić ["type", cmd]na"type " + cmd
MestreLion

4
Uwaga: upewnij się, że zmienna „cmd” zawiera prawidłowe dane. Jeśli pochodzi z zewnętrznego źródła, zły facet może dać ci „ls; rm -rf /”. Myślę, że rozwiązanie w pythonie (bez podprocesu) jest znacznie lepsze. Następny punkt: Jeśli często wywołujesz tę metodę, rozwiązanie podprocesu jest znacznie wolniejsze, ponieważ wiele procesów wymaga odrodzenia.
guettli

7

Zobacz moduł os.path , aby uzyskać przydatne funkcje w ścieżkach. Aby sprawdzić, czy istniejący plik jest wykonywalny, użyj os.access (ścieżka, tryb) w trybie os.X_OK.

os.X_OK

Wartość do włączenia w parametr mode parametru access () w celu ustalenia, czy ścieżka może zostać wykonana.

EDYCJA: W sugerowanych which()implementacjach brakuje jednej wskazówki - używanej os.path.join()do budowania pełnych nazw plików.


Dzięki, gimel, więc w zasadzie mam swoją odpowiedź: nie ma takiej funkcji, muszę to zrobić ręcznie.
Piotr Lesnicki,

Nie używaj os.access. funkcja dostępu jest przeznaczona dla programów suid.
Changming Sun

6

Na podstawie tego, że łatwiej jest poprosić o wybaczenie niż o pozwolenie , po prostu spróbuję go użyć i złapać błąd (w tym przypadku OSError - sprawdziłem, czy plik nie istnieje i plik nie jest wykonywalny, a oba dają OSError).

Pomaga, jeśli plik wykonywalny ma coś w rodzaju --versionflagi, która szybko uniemożliwia działanie.

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

Nie jest to ogólne rozwiązanie, ale będzie najłatwiejszym sposobem dla wielu przypadków użycia - tych, w których kod musi szukać jednego dobrze znanego pliku wykonywalnego.


3
Zbyt niebezpieczne jest nawet wywoływanie --versionprogramu o nazwie launchmissiles!
xApple

1
+1, podoba mi się to podejście. EAFP jest złotą regułą Pythona. Może z wyjątkiem konfiguracji interfejsu użytkownika, dlaczego chcesz wiedzieć, czy launchmissiesistnieje, chyba że chcesz wystrzelić pociski? Lepiej go wykonać i działać zgodnie ze statusem wyjścia / wyjątkami
MestreLion

Problem z tą metodą polega na tym, że dane wyjściowe są drukowane na konsoli. Jeśli użyjesz potoków i powłoki = Prawda, błąd OSError nigdy się nie podniesie
Nick Humrich

W systemie macOS masz również pliki wykonywalne stub, np. gitŻe prawdopodobnie nie chcesz uruchamiać się na ślepo.
Bob Aman

5

Wiem, że jestem tu trochę nekromantą, ale natknąłem się na to pytanie i zaakceptowane rozwiązanie nie działało dla mnie we wszystkich przypadkach. Pomyślałem, że i tak warto je złożyć. W szczególności wykrywanie trybu „wykonywalnego” i wymóg podania rozszerzenia pliku. Ponadto, zarówno python3.3 shutil.which(używa PATHEXT), jak i python2.4 + distutils.spawn.find_executable(tylko próbuje dodać '.exe') działają tylko w podzbiorze przypadków.

Napisałem więc „super” wersję (na podstawie zaakceptowanej odpowiedzi i PATHEXTsugestii Suraja). Ta wersja whichwykonuje to zadanie nieco dokładniej i najpierw wypróbowuje szereg technik „szerokofazowych”, a ostatecznie próbuje bardziej szczegółowych wyszukiwań w PATHprzestrzeni:

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

Sposób użycia wygląda następująco:

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

Przyjętego rozwiązania nie działa dla mnie w tym przypadku, ponieważ nie były plików, takich jak meld.1, meld.ico, meld.doapitp również w katalogu, z których jedno zostało zwróconych zamiast (przypuszczalnie od leksykograficznie pierwszy) bo wykonywalny testy w przyjętym odpowiedź była niepełna i daje fałszywie pozytywne.



2

W StackOverflow znalazłem coś, co rozwiązało problem. Działa to pod warunkiem, że plik wykonywalny ma opcję (np. --Help lub --version), która wyświetla coś i zwraca wartość wyjścia równą zero. Zobacz Pomijanie danych wyjściowych w wywołaniach Pythona w plikach wykonywalnych - „wynik” na końcu fragmentu kodu w tej odpowiedzi wyniesie zero, jeśli plik wykonywalny znajduje się na ścieżce, w przeciwnym razie najprawdopodobniej będzie to 1.


2

Wydaje się to dość proste i działa zarówno w Pythonie 2, jak i 3

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

Niestety, Jaap, ale to rozwiązanie działa tylko wtedy, gdy plik wykonywalny nie wywołuje kodu wyjścia 1, jeśli zostanie wywołany niepoprawnie. Na przykład będzie działał dla „dir” i „ls”, ale jeśli wykonasz coś, co wymaga konfiguracji, to się zepsuje, nawet jeśli plik wykonywalny jest dostępny.
Spedge,

1
Co dokładnie rozumiesz przez „wymaganie konfiguracji”? Samo „które” tak naprawdę nie wykonuje niczego, a jedynie sprawdza ŚCIEŻKĘ pod kątem istnienia pliku wykonywalnego o tej nazwie (man who).
jaap

1
Och, więc używasz „które”, aby znaleźć plik wykonywalny. Więc to działa tylko na Linux / Unix?
Spedge

1
Użyj command -v executablelub, type executableaby być uniwersalnym. Są przypadki, w których na komputerach Mac nie zwraca się oczekiwanych wyników.
RJ

1

Ważnym pytaniem jest „ Dlaczego musisz sprawdzić, czy istnieje plik wykonywalny?” Może nie? ;-)

Ostatnio potrzebowałem tej funkcji, aby uruchomić przeglądarkę plików PNG. Chciałem przeprowadzić iterację nad niektórymi predefiniowanymi przeglądarkami i uruchomić pierwszą, która istnieje. Na szczęście trafiłem os.startfile. Jest dużo lepiej! Prosty, przenośny i korzysta z domyślnej przeglądarki w systemie:

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

Aktualizacja: Myliłem się co os.startfiledo przenośności ... To tylko system Windows. Na Macu musisz uruchomić openpolecenie. I xdg_openna Uniksie. Podczas dodawania obsługi komputerów Mac i Unix występuje problem w języku Pythonos.startfile .



1

Dodano obsługę systemu 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

możesz stwierdzić, czy plik istnieje z modułem OS. w szczególności plik wykonywalny wydaje się dość niesportowalny, biorąc pod uwagę, że wiele elementów jest wykonywalnych w systemie nix, których nie ma w systemie Windows i odwrotnie.


0

Wydaje się, że oczywistym wyborem jest „które”, analizując wyniki za pomocą popen, ale można to zasymulować w inny sposób, używając klasy os. W pseudopytonie wyglądałoby to tak:

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

Byłbym ostrożny z uruchomieniem komendy „która” za pomocą os.exec lub czegoś podobnego. Często jest to nie tylko powolne (jeśli wydajność ma jakiekolwiek znaczenie), ale jeśli używasz zmiennej jako części łańcucha wykonawczego, bezpieczeństwo staje się problemem. Ktoś mógłby się zakraść w „rm -rf /”.
Parappa,

1
Które, ponieważ użylibyśmy funkcji os.popen do uruchomienia polecenia what utworzonego przez program, tak naprawdę nie ma zastosowania, prawda?
Charlie Martin,

2
Dzięki, ale nie jestem pewien, czy „które” istnieje w systemie Windows i podobnych. Chciałem zasadniczo wiedzieć, czy w standardowej bibliotece istnieje coś fantazyjnego
Piotr Lesnicki,

W standardowych instalacjach Windows nadal nie ma whichpolecenia; istnieje wersja UnxUtils, ale musisz znać / określić rozszerzenie, w przeciwnym razie program nie zostanie znaleziony.
Tobias

0

Zasadniczo chcesz znaleźć plik w zamontowanym systemie plików (niekoniecznie tylko w katalogach PATH) i sprawdzić, czy jest wykonywalny. Przekłada się to na następujący plan:

  • wyliczyć wszystkie pliki w lokalnie zamontowanych systemach plików
  • dopasuj wyniki do wzorca nazwy
  • dla każdego znalezionego pliku sprawdź, czy jest wykonywalny

Powiedziałbym, że wykonanie tego w sposób przenośny będzie wymagało dużej mocy obliczeniowej i czasu. Czy to naprawdę to, czego potrzebujesz?


0

W standardowej dystrybucji języka Python znajduje się skrypt what.py (np. W systemie Windows '\PythonXX\Tools\Scripts\which.py').

EDYCJA: which.pyzależy od tego, lsdlatego nie jest wieloplatformowa.


0

Żaden z poprzednich przykładów nie działa na wszystkich platformach. Zwykle nie działają one w systemie Windows, ponieważ można wykonać bez rozszerzenia pliku i zarejestrować nowe rozszerzenie. Na przykład w systemie Windows, jeśli Python jest dobrze zainstalowany, wystarczy uruchomić plik „file.py” i będzie działać.

Jedynym prawidłowym i przenośnym rozwiązaniem, jakie miałem, było wykonanie polecenia i wyświetlenie kodu błędu. Każdy przyzwoity plik wykonywalny powinien mieć zestaw parametrów wywoływania, które nic nie zrobią.


-3

Za pomocą biblioteki tkanin 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
To bardzo zła sugestia. Dosłownie uzależniasz program od zdalnej biblioteki wykonawczej w celu odrodzenia programu lokalnego (co Python stdlib może z łatwością zrobić), a dodatkowo zależy to od tego, which(1)który nie jest obecny we wszystkich systemach.
Michał Górny,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.