Перевірте, чи виконується виконуваний файл у 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.