Перевірте, чи виконується виконуваний файл у Python?


297

Чи існує портативний та простий спосіб перевірити, чи існує виконувана програма?

Під простим я маю на увазі щось на кшталт whichкоманди, яка була б просто досконалою. Я не хочу вручну шукати PATH або щось, що стосується спроби виконати його за допомогою Popen& al, і побачити, чи не вдалося це зробити (це я зараз роблю, але уявіть, що це launchmissiles)


4
Що не так у пошуку змінної середовища PATH? Як ви думаєте, яка команда UNIX "який" робить?
Джей

1
Чи є скрипт Which.py ​​із stdlib простим способом?
jfs

@JF - скрипт what.py, включно з Python залежить від 'ls', а деякі інші коментарі вказують на те, що Пьотр шукав відповідь між платформами.
Джей

@Jay: Дякую за коментар. У мене встановлені coreutils у Windows, тому я не помітив, що Which.py ​​є специфічним для Unix.
jfs

Є також whichсторонній модуль: code.activestate.com/pypm/which
Шрідхар Ратнакумар

Відповіді:


321

Найпростіший спосіб, про який я можу придумати:

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

Редагувати : оновлений зразок коду, щоб включити логіку для обробки випадку, коли наданий аргумент вже є повним шляхом до виконуваного файлу, тобто "that / bin / ls". Це імітує поведінку команди UNIX 'that'.

Редагувати : оновлено для використання os.path.isfile () замість os.path.exists () за коментарями.

Редагувати : path.strip('"')здається, що тут неправильно робити. Ні Windows, ні POSIX не підтримують цитовані елементи PATH.


Дякую Джей, я приймаю твою відповідь, хоча для мене це відповідає негативно. Жодної такої функції в лібсах немає, я просто мушу її написати (я визнаю, моя рецептура була недостатньо чіткою в тому, що я знаю, що робить).
Петро Лесницький

1
Джей, якщо ти доповниш свою відповідь відповідно до моєї (щоб мати повне "w"), щоб я міг видалити свою.
Петро Лесницький

2
Для деяких ОС вам може знадобитися додати розширення виконуваного файлу. Наприклад, на Ubuntu я можу написати, що ("scp"), але в Windows, мені потрібно було написати, що ("scp.exe").
вафлеман

13
Я б запропонував змінити "os.path.exists" на "os.path.isfile". Інакше в Unix це може помилково збігатися з каталогом із набором бітів + ​​x. Я також вважаю корисним додати це до початку функції: import sys; якщо sys.platform == "win32", а не program.endswith (". exe"): program + = ".exe". Таким чином, у Windows ви можете посилатися на "calc" або "calc.exe", як і у вікні cmd.
Кевін Іварсен

1
@KevinIvarsen Кращим варіантом буде прокручування значень PATHEXTenv var, тому що commandце так само, command.comяк і scriptпротиscript.bat
Lekensteyn

325

Я знаю, що це давнє питання, але ви можете використовувати distutils.spawn.find_executable. Це було задокументовано ще з python 2.4 та існує з python 1.6.

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

Також тепер пропонує Python 3.3 shutil.which().


7
Увімкнення win32, distutils.spawn.find_executableреалізація шукає лише .exeзамість використання списку розширень для пошуку налаштування %PATHEXT%. Це не чудово, але це може спрацювати у всіх випадках, коли хтось потребує.
rakslice

7
Приклад використання:from distutils import spawn php_path = spawn.find_executable("php")
codefreak

6
Мабуть distutils.spawn, недоступно надійно: з моєю установкою системи (/ usr / bin / python) Python 2.7.6 на OS X 10.10, я отримую: AttributeError: 'module' object has no attribute 'spawn'хоч як не дивно він працює на тій же машині з тією ж версією Python, але з встановити virtualenv.
Джош Купершмідт

8
@JoshKupershmidt, переконайтесь у тому import distutils.spawn, чи дотримуйтесь from distutils import spawnсинтаксису, а не просто import distutils. В іншому випадку він може бути недоступним, і ви отримаєте вищезазначене, AttributeErrorнавіть якщо воно є.
Іоанна Св. Іоанна


39

Для python 3.2 та новіших версій:

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

Це однорядний варіант відповіді Джея , також тут як функція лямбда:

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

Або, нарешті, відступ як функція:

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

Для python 3.3 та новіших версій:

import shutil

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

Як відповідь Яна-Філіпа Герка :

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

Як деф:

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

1
версія "відрізка як функція" використовує змінну xтам, де вона повинна бутиcmd
0x89

Ви також повинні додати тест, щоб побачити, чи os.path.join(path, cmd)є файл, ні? Крім того, каталоги можуть також встановити виконуваний біт ...
MestreLion

@MestreLion Це виглядає як можливий випадок, ви б заперечили підтвердити цю поведінку та оновити цю відповідь? Я радий змінити цю публікацію на вікі спільноти, якщо це допоможе.
ThorSummoner

1
@ThorSummoner: Я підтвердив це, і він дійсно вимагає тесту для файлу. Простий тест: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(...)у відповідні місця достатньо, щоб виправити це
MestreLion

19

Просто не забудьте вказати розширення файлу у Windows. В іншому випадку вам доведеться написати набагато складніше is_exeдля Windows, використовуючи PATHEXTзмінну середовища. Можливо, ви просто захочете використовувати FindPath .

ОТОХ, чому ти взагалі турбуєшся шукати виконуваний файл? Операційна система зробить це за вас як частина popenвиклику & створить виняток, якщо виконуваний файл не знайдеться. Все, що вам потрібно зробити - це зловити правильний виняток для даної ОС. Зауважте, що в Windows, subprocess.Popen(exe, shell=True)мовчки вийде з ладу, якщо exeйого не знайдено.


Включення PATHEXTу вищезазначене впровадження which(у відповіді Джея):

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
Він вирішив помилку у прийнятій відповіді, відчуваю, що ця відповідь має бути зверху.
NiTe Luo

розумне використання yieldв ext_candidates, дало мені краще зрозуміти, як працює це ключове слово
Грант Хамфріс

15

Для * nix-платформ (Linux та OS X)

Це, здається, працює для мене:

Відредагований для роботи в Linux, завдяки Mestreion

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

Ми робимо тут вбудовану команду typeта перевірку коду виходу. Якщо такої команди немає,type вийде з 1 (або з нульовим кодом статусу все одно).

Трохи про stdout та stderr - це лише приглушити вихід typeкоманди, оскільки нас цікавить лише код статусу виходу.

Приклад використання:

>>> 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
Ви впевнені, що це працює? Це дуже приємний підхід, але typeце вбудована оболонка, а не виконуваний файл, тому subprocess.call()тут не вдається.
MestreLion

1
Ви пробували це чи просто теоретизуєте? Це працює на моєму комп'ютері все одно.
hasen

Я спробував це в Ubuntu 12.04, це кидає OSError: [Errno 2] No such file or directory. Можливо, в Mac typeсправжня команда
MestreLion

2
Після ЛОТ тестування, я знайшов , як виправити: додати shell=Trueі замінити ["type", cmd]на"type " + cmd
MestreLion

4
Увага: переконайтеся, що змінна "cmd" містить дійсні дані. Якщо він надходить із зовнішнього джерела, поганий хлопець може дати вам "ls; rm -rf /". Я думаю, що рішення в пітоні (без підпроцесу) набагато краще. Наступний момент: Якщо ви часто називаєте цей метод, рішення підпроцесу набагато повільніше, оскільки потрібно створити багато процесів.
guettli

7

Див. Модуль os.path щодо деяких корисних функцій у іменах шляхів. Щоб перевірити, чи наявний файл виконується, використовуйте os.access (шлях, режим) у режимі os.X_OK.

os.X_OK

Значення для включення в параметр режиму доступу (), щоб визначити, чи можна виконати шлях.

EDIT: У запропонованих which()реалізаціях відсутня одна підказка - використання os.path.join()для побудови повних імен файлів.


Дякую, гімеле, так що в основному я маю свою відповідь: такої функції немає, я повинен це робити вручну.
Петро Лесницький

Не використовуйте os.access. функція доступу призначена для програм suid.
Changming Sun

6

Виходячи з того, що простіше просити прощення, ніж дозволу я б просто спробував ним скористатися і виявити помилку (в цьому випадку OSError - я перевірив, чи файл не існує, а файл не виконується, і вони обидва дають OSError).

Це допомагає, якщо у виконуваному файлі є щось на зразок --versionпрапора, який є швидким режимом відключення.

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

Це не є загальним рішенням, але це буде найпростіший спосіб для багатьох випадків використання - тих, де в коді потрібно шукати єдиний добре відомий виконуваний файл.


3
Це занадто небезпечно навіть дзвонити --versionна програму з назвою launchmissiles!
xApple

1
+1, мені подобається такий підхід. EAFP - це золоте правило Python. Окрім можливо налаштування інтерфейсу, чому ви хочете знати, чи launchmissiesіснує, якщо ви не хочете запускати ракети? Краще виконати його та діяти після статусу виходу / винятку
MestreLion

Проблема цього методу полягає в тому, що вихід друкується на консоль. Якщо ви використовуєте труби та оболонку = True, тоді OSError ніколи не піднімається
Нік Гумріч

На macOS у вас також є виконавчі файли заглушок, наприклад, gitщо, ймовірно, ви не хочете запускати сліпо.
Боб Аман

5

Я знаю, що я тут трохи некроман, але я натрапив на це питання, і прийняте рішення не працювало для мене для всіх випадків. Я вважав, що це може бути корисно подати в будь-якому випадку. Зокрема, виявлення "виконуваного" режиму та вимога подання розширення файлу. Крім того, і python3.3 shutil.which(використовує PATHEXT), і python2.4 + distutils.spawn.find_executable(просто намагається додати '.exe') працюють лише в підмножині випадків.

Тому я написав "супер" версію (на основі прийнятої відповіді та PATHEXTпропозиції Сурая). Ця версія whichвиконує завдання дещо ретельніше, і спершу спробує серію "широкофазних" методів ширини, а в кінцевому підсумку намагається виконати більш дрібні пошуки в PATHпросторі:

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

Використання виглядає приблизно так:

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

Прийняте рішення не зробило роботу для мене в цьому випадку, так як там було файли , такі як meld.1, meld.ico, meld.doapі т.д. , також в каталозі, один з яких були повернуті замість (імовірно , так як лексично першого) , так як виконується тест в загальноприйнятому відповіді був неповним і дають помилкові позитиви.



2

Я знайшов щось у StackOverflow, що вирішило проблему для мене. Це працює за умови, що у виконавчого файлу є параметр (наприклад --help або --version), який виводить щось і повертає статус виходу в нуль. Див. « Придушити вихід» у викликах Python до виконуваних файлів - «результат» в кінці фрагмента коду у цій відповіді буде нульовим, якщо виконуваний файл знаходиться в шляху, інакше, швидше за все, це 1.


2

Це здається досить простим і працює як в python 2, так і в 3

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

Вибачте Jaap, але це рішення працює лише тоді, коли виконуваний файл не викликає код виходу 1, якщо він викликаний неправильно. Так, наприклад, він буде працювати для "dir" і "ls", але якщо ви виконаєте проти чогось, що вимагає конфігурації, він зламається, навіть якщо виконуваний файл є.
Шпег

1
Що саме ви маєте на увазі під "вимагати конфігурації"? Сам по собі "який" насправді нічого не виконує, а просто перевіряє PATH на наявність виконавчого файлу за цим іменем (man who).
jaap

1
О, значить, ви використовуєте "який" для того, щоб знайти виконуваний файл. Отже, це працює лише для Linux / Unix?
Шпег

1
Використовувати command -v executableабо type executableбути універсальним. Бувають випадки, коли на Macs не повертають очікуваних результатів.
RJ

1

Важливе питання " Чому потрібно перевірити, чи існує виконуваний файл?" Можливо, ви цього не робите? ;-)

Нещодавно мені потрібна була ця функція, щоб запустити програму перегляду PNG-файлу. Я хотів переглядати деякі наперед визначені глядачі та запустити перше, що існує. На щастя, я натрапив os.startfile. Це набагато краще! Простий, портативний і використовує переглядач за замовчуванням у системі:

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

Оновлення: Я помилявся з приводу os.startfileпереносності ... Це лише Windows. На Mac потрібно запустити openкоманду. І xdg_openна Unix. Існує проблема Python щодо додавання Mac та Unix підтримки для os.startfile.



1

Додана підтримка 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

Ви можете сказати, чи існує файл з модулем os. зокрема виконується виконуваний файл, враховуючи, що багато речей виконуються на nix, які не є у Windows, і навпаки.


0

Здавалося б, очевидним вибором є "який", аналізуючи результати через popen, але ви могли б імітувати його інакше, використовуючи клас os. У псевдопітоні це виглядатиме так:

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

Я був би обережний щодо запуску команди "that", використовуючи os.exec або щось подібне. Це не тільки повільно (якщо продуктивність викликає будь-які проблеми), але якщо ви використовуєте змінну як частину вашого рядка exec, безпека стає проблемою. Хтось міг прокрастись у "rm -rf /".
Параппа

1
Що, оскільки ми використовували функцію os.popen для запуску команди, створеної програмою, насправді не застосовується, ні?
Чарлі Мартін

2
Дякую, але я не впевнений, чи існує "що" на Windows і подібне. По суті, я хотів знати, чи є щось фантазійне в стандартній зоні
Пьотр Лесницький

У стандартних установках Windows досі немає whichкоманди; є версія UnxUtils, але ви повинні знати / вказати розширення, інакше програма не буде знайдена.
Тобіас

0

Тому в основному ви хочете знайти файл у змонтованій файловій системі (не обов'язково лише в каталогах PATH) і перевірити, чи він виконується. Це означає наступний план:

  • перерахувати всі файли в локально встановлених файлових системах
  • відповідати результатам з малюнком імен
  • для кожного знайденого файлу перевірте, чи він виконується

Я б сказав, що для цього на портативний спосіб знадобиться багато обчислювальної потужності та часу. Це дійсно те, що вам потрібно?


0

Існує скрипт that.py у стандартному дистрибутиві Python (наприклад, у Windows '\PythonXX\Tools\Scripts\which.py').

EDIT: which.pyвід lsцього залежить, тому це не є кросплатформою.


0

Жоден з попередніх прикладів не працює на всіх платформах. Зазвичай вони не працюють у Windows, оскільки їх можна виконати без розширення файлів і можете зареєструвати нове розширення. Наприклад, у Windows, якщо python добре встановлений, достатньо виконати "file.py", і він буде працювати.

Єдиним правильним і портативним рішенням, яке я мав, було виконання команди і перегляд коду помилки. Будь-який гідний виконуваний файл повинен мати набір параметрів виклику, які нічого не дадуть.


-3

Використання тканинної бібліотеки 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
Це дуже погана пропозиція. Ви буквально робите програму залежною від бібліотеки віддаленого виконання для нерестування локальної програми (що Python stdlib може зробити легко), а крім того, ви залежите від того, which(1)яка присутня не у всіх системах.
Michał Górny
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.