Проверить, существует ли исполняемый файл в Python?


297

В Python есть ли портативный и простой способ проверить, существует ли исполняемая программа?

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


4
Что не так с поиском переменной среды PATH? Как вы думаете, что делает UNIX «какая» команда?
— Джей

1
Является ли скрипт which.py ​​из stdlib простым способом?
— JFS

@JF - скрипт which.py ​​вкл. с Python зависит от 'ls', и некоторые другие комментарии указывают, что Петр искал кроссплатформенный ответ.
— Джей

@Jay: Спасибо за комментарий. Я установил coreutils в Windows, поэтому я не заметил, какой именно .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

Редактировать : Обновлен пример кода, чтобы включить логику для обработки случая, когда предоставленный аргумент уже является полным путем к исполняемому файлу, т.е. Это имитирует поведение UNIX-команды 'which'.

Изменить : Обновлено для использования 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; if sys.platform == "win32", а не program.endswith (". exe"): program + = ".exe". Таким образом, в Windows вы можете ссылаться либо на «calc», либо на «calc.exe», точно так же, как в окне cmd.
— Кевин Иварсен

1
@KevinIvarsen Лучшим вариантом будет циклическое переключение значений PATHEXTenv var, потому что commandоно так же верно, как command.comи scriptvsscript.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%. Это не очень хорошо, но это может работать для всех случаев, которые кому-то нужны.
— Ракслице

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, но из виртуальная установка.
— Джош Купершмидт

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

Как один из вкладышей Ян-Филип Gehrcke Ответ :

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для окон PATHEXTпеременную окружения. Вы можете просто использовать FindPath .

OTOH, почему вы вообще пытаетесь найти исполняемый файл? Операционная система сделает это за вас как часть 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

умное использование yieldin 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
Вы пробовали это или просто теоретизируете? Это работает на моем Mac в любом случае.
— Хазен

Я пробовал это в 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 /». Я думаю, что решение в Python (без подпроцесса) намного лучше. Следующий пункт: если вы будете часто вызывать этот метод, решение для подпроцесса будет намного медленнее, поскольку нужно запускать множество процессов.
— Геттли

7

Смотрите модуль os.path для некоторых полезных функций по путям. Чтобы проверить, является ли существующий файл исполняемым, используйте os.access (путь, режим) в режиме os.X_OK.

os.X_OK

Значение, которое нужно включить в параметр mode access (), чтобы определить, может ли быть выполнен путь.

РЕДАКТИРОВАТЬ: В предлагаемых which()реализациях отсутствует одна подсказка - использование os.path.join()для создания полных имен файлов.


Спасибо, гимел, так что в основном у меня есть ответ: такой функции не существует, я должен сделать это вручную.
— Петр Лесницкий

Не используйте os.access. Функция доступа предназначена для программ suid.
— Чанминг Сан

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

Проблема этого метода в том, что вывод выводится на консоль. Если вы используете pipe и shell = 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», но если вы выполняете что-то, требующее настройки, он сломается, даже если исполняемый файл есть.
— Spedge

1
Что вы подразумеваете именно под «требуется настройка»? Само по себе «которое» на самом деле ничего не выполняет, а просто проверяет PATH на наличие исполняемого файла с этим именем (man which).
— Яап

1
Ооо, так что вы используете «который» для того, чтобы найти исполняемый файл. Так это работает только для Linux / Unix?
— Spedge

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

Я бы осторожно запустил команду "which", используя os.exec или что-то в этом роде. Мало того, что это часто медленно (если производительность вызывает какие-то проблемы), но если вы используете переменную как часть вашей строки exec, безопасность становится проблемой. Кто-нибудь может прокрасться в "rm -rf /".
— Параппа

1
Который, поскольку мы будем использовать функцию os.popen для запуска какой команды, созданной программой, на самом деле не применяется, нет?
— Чарли Мартин

2
Спасибо, но я не уверен, существует ли «которое» на окнах и тому подобное. По сути, я хотел узнать, существует ли что-то необычное в стандартной
— библиотеке

В стандартных установках Windows все еще нет whichкоманды; Существует версия UnxUtils, но вы должны знать / указать расширение, иначе программа не будет найдена.
— Тобиас

0

Поэтому в основном вы хотите найти файл в смонтированной файловой системе (не обязательно только в каталогах PATH) и проверить, является ли он исполняемым. Это переводит на следующий план:

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

Я бы сказал, что это портативное решение потребует больших вычислительных ресурсов и времени. Это действительно то, что вам нужно?


0

В стандартном дистрибутиве Python есть скрипт which.py (например, в Windows'\PythonXX\Tools\Scripts\which.py' ) .

РЕДАКТИРОВАТЬ: which.pyзависит от, lsпоэтому он не кроссплатформенный.


0

Ни один из предыдущих примеров не работает на всех платформах. Обычно они не работают на Windows, потому что вы можете выполнить без расширения файла и что вы можете зарегистрировать новое расширение. Например, в Windows, если python хорошо установлен, достаточно запустить file.py, и он будет работать.

Единственное верное и переносимое решение, которое у меня было, - выполнить команду и увидеть код ошибки. Любой приличный исполняемый файл должен иметь набор параметров вызова, который ничего не будет делать.


-3

Используя библиотеку Python Fabric:

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)какая из них присутствует не во всех системах.
— Михал Гурни
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.