Проверить, существует ли исполняемый файл в 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
ThorSummoner
источник
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
Hasen
источник
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.

Somesh
источник
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

Вы можете попробовать внешнюю библиотеку под названием "sh" ( http://amoffat.github.io/sh/ ).

import sh
print sh.which('ls')  # prints '/bin/ls' depending on your setup
print sh.which('xxx') # prints None
Юнг Роу
источник
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) и проверить, является ли он исполняемым. Это переводит на следующий план:

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

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

Zgoda
источник
0

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

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

JFS
источник
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
frodopwns
источник
2
Это очень плохое предложение. Вы буквально ставите программу в зависимость от библиотеки удаленного выполнения, которая порождает локальную программу (что может легко сделать Python stdlib), и, кроме того, вы зависите от того, which(1)какая из них присутствует не во всех системах.
Михал Гурни