Testar se existe executável em Python?

297

No Python, existe uma maneira portátil e simples de testar se existe um programa executável?

Por simples, quero dizer algo como o whichcomando que seria perfeito. Eu não quero procurar PATH manualmente ou algo que envolva tentar executá-lo com Popen& al e ver se ele falha (é o que estou fazendo agora, mas imagine que seja launchmissiles)

Piotr Lesnicki
fonte
4
O que há de errado em pesquisar a variável de ambiente PATH? O que você acha que o comando UNIX 'what' faz?
Jay
1
O script which.py ​​do stdlib é uma maneira simples?
jfs
@JF - o script which.py ​​incl. com Python depende de 'ls' e alguns dos outros comentários indicam que Piotr estava procurando uma resposta entre plataformas.
22468 Jay
@ Jay: Obrigado pelo comentário. Eu tenho o coreutils instalado no Windows, então não notei o arquivo what.py é específico do unix.
JFS
Há também which, o módulo de terceiros: code.activestate.com/pypm/which
Sridhar Ratnakumar

Respostas:

321

A maneira mais fácil de pensar:

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

Editar : amostra de código atualizada para incluir lógica para lidar com o caso em que o argumento fornecido já é um caminho completo para o executável, ou seja, "what / bin / ls". Isso imita o comportamento do comando UNIX 'what'.

Editar : atualizado para usar os.path.isfile () em vez de os.path.exists () por comentários.

Edit : path.strip('"')parece que a coisa errada a fazer aqui. Nem o Windows nem o POSIX parecem incentivar os itens PATH citados.

Jay
fonte
Obrigado Jay, aceito sua resposta, embora, para mim, responda minha pergunta pelo negativo. Nenhuma função existe nas bibliotecas, eu apenas tenho que escrevê-la (admito que minha formulação não foi clara o suficiente no fato de eu saber o que faz).
Piotr Lesnicki
1
Jay, se você completar sua resposta de acordo com a minha (para ter 'w' completo), para que eu possa remover a minha.
Piotr Lesnicki
2
Para alguns sistemas operacionais, pode ser necessário adicionar a extensão do executável. Por exemplo, no Ubuntu eu posso escrever qual ("scp"), mas no Windows, eu preciso escrever qual ("scp.exe").
9309 waffleman
13
Eu sugiro alterar "os.path.exists" para "os.path.isfile". Caso contrário, no Unix, isso pode corresponder falsamente a um diretório com o conjunto de bits + x. Também acho útil adicionar isso ao topo da função: import sys; se sys.platform == "win32" e não program.endswith (". exe"): programa + = ".exe". Dessa forma, no Windows, você pode consultar "calc" ou "calc.exe", como faria em uma janela do cmd.
22411 Kevin Ivarsen
1
@KevinIvarsen A melhor opção seria looping através dos valores do PATHEXTenv var porque commandé tão válida quanto command.comcomo é scriptvsscript.bat
Lekensteyn
325

Eu sei que esta é uma pergunta antiga, mas você pode usar distutils.spawn.find_executable. Isso foi documentado desde o python 2.4 e existe desde o python 1.6.

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

Além disso, o Python 3.3 agora oferece shutil.which().

Nathan Binkert
fonte
7
Em win32, a distutils.spawn.find_executableimplementação procura apenas em .exevez de usar a lista de extensões para procurar a configuração %PATHEXT%. Isso não é ótimo, mas pode funcionar para todos os casos de que alguém precisa.
rakslice
7
exemplo de uso:from distutils import spawn php_path = spawn.find_executable("php")
codefreak
6
Aparentemente, distutils.spawnnão está disponível de maneira confiável: com minha instalação do sistema (/ usr / bin / python) do Python 2.7.6 no OS X 10.10, recebo AttributeError: 'module' object has no attribute 'spawn':, embora estranhamente funcione na mesma máquina com a mesma versão do Python, mas em uma instalação virtualenv.
Josh Kupershmidt 10/0316
8
@JoshKupershmidt, certifique-se de import distutils.spawnseguir a from distutils import spawnsintaxe, e não apenas import distutils. Caso contrário, pode não estar acessível e você obterá o que foi mencionado acima, AttributeErrormesmo que esteja lá.
John St. John
39

Para python 3.2 e versões anteriores:

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

Esta é uma linha da resposta de Jay , também aqui como uma função 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')

Ou, por último, recuado como uma função:

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

Para python 3.3 e posterior:

import shutil

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

Como uma linha de resposta de Jan-Philip Gehrcke :

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

Como um def:

def cmd_exists(cmd):
    return shutil.which(cmd) is not None
ThorSummoner
fonte
1
a versão "recuado como uma função" usa a variável xonde deveria estarcmd
0x89
você também deve adicionar um teste para ver se os.path.join(path, cmd)é um arquivo, não? Afinal, diretórios também pode ter o conjunto de bits executável ...
MestreLion
@MestreLion Parece um possível caso, você se importaria de confirmar esse comportamento e atualizar esta resposta? Fico feliz em mudar este post para um wiki da comunidade, se isso ajudar.
ThorSummoner
1
@ ThorSummoner: Eu confirmei, e realmente exige o teste para o arquivo. Um teste simples: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
Adicionando um simples and os.path.isfile(...)para os lugares apropriados é suficiente para correção
MestreLion
19

Lembre-se de especificar a extensão do arquivo no Windows. Caso contrário, você precisará escrever um tópico muito complicado is_exepara o Windows usando PATHEXTa variável de ambiente. Você pode apenas querer usar o FindPath .

OTOH, por que você está se preocupando em procurar o executável? O sistema operacional fará isso por você como parte da popenchamada e gerará uma exceção se o executável não for encontrado. Tudo o que você precisa fazer é capturar a exceção correta para o SO fornecido. Observe que, no Windows, subprocess.Popen(exe, shell=True)falhará silenciosamente se exenão for encontrado.


Incorporando PATHEXTna implementação acima de which(na resposta de Jay):

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
Suraj
fonte
1
Foi corrigido um erro na resposta aceita; sinta que esta resposta deve estar no topo.
NiTe Luo 19/09/17
o uso inteligente de yieldnos ext_candidates, deu-me uma melhor compreensão de como que as obras de palavra-chave
Grant Humphries
15

Para plataformas * nix (Linux e OS X)

Isso parece estar funcionando para mim:

Editado para funcionar no Linux, graças a Mestreion

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

O que estamos fazendo aqui é usar o comando embutido typee verificar o código de saída. Se não houver esse comando, typesairá com 1 (ou um código de status diferente de zero).

A parte sobre stdout e stderr é apenas para silenciar a saída do typecomando, pois estamos interessados ​​apenas no código de status de saída.

Exemplo de uso:

>>> 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
fonte
2
Tem certeza de que isso funciona? É uma abordagem muito boa, mas typeé um shell embutido, não um arquivo executável, portanto subprocess.call()falha aqui.
MestreLion
1
Você já tentou ou está apenas teorizando? Funciona de qualquer maneira no meu mac.
hasen 23/05
Eu tentei no Ubuntu 12.04, lança OSError: [Errno 2] No such file or directory. Talvez no Mac typeé um comando real
MestreLion
2
Depois de um monte de testes, eu descobri como corrigir: add shell=Truee substituir ["type", cmd]por"type " + cmd
MestreLion
4
Atenção: verifique se a variável "cmd" contém dados válidos. Se vier de uma fonte externa, um bandido poderá fornecer "ls; rm -rf /". Eu acho que a solução em python (sem subprocesso) é muito melhor. Próximo ponto: se você chamar esse método frequentemente, a solução do subprocesso será muito mais lenta, pois muitos processos precisam ser gerados.
guettli
7

Veja o módulo os.path para algumas funções úteis nos nomes de caminho. Para verificar se um arquivo existente é executável, use os.access (caminho, modo) , com o modo os.X_OK.

os.X_OK

Valor a incluir no parâmetro mode de access () para determinar se o caminho pode ser executado.

Edição: As which()implementações sugeridas estão faltando uma pista - usando os.path.join()para criar nomes de arquivos completos.

gimel
fonte
Obrigado, gimel, então basicamente tenho a minha resposta: não existe essa função, devo fazê-la manualmente.
Piotr Lesnicki
Não use os.access. A função de acesso foi projetada para programas suid.
Changming Sun
6

Com base em que é mais fácil pedir perdão do que permissão, eu apenas tentaria usá-lo e capturar o erro (neste caso, OSError - verifiquei se o arquivo não existe e o arquivo não é executável e ambos fornecem o OSError).

Ajuda se o executável tem algo como um --versionsinalizador que é um rápido não funcionamento.

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

Essa não é uma solução geral, mas será a maneira mais fácil para muitos casos de uso - aqueles em que o código precisa procurar um único executável conhecido.

Hamish Downer
fonte
3
É muito perigoso até mesmo chamar --versionum programa chamado launchmissiles!
XApple
1
+1, eu gosto dessa abordagem. O EAFP é uma regra de ouro do Python. Exceto, talvez, a configuração da interface do usuário, por que você gostaria de saber se launchmissiesexiste, a menos que queira lançar mísseis? Melhor para executá-lo e agir de acordo com status de saída / exceções
MestreLion
O problema com esse método é que a saída é impressa no console. Se você usar tubos e shell = True, então o OSError nunca é levantada
Nick Humrich
No macOS, você também tem executáveis ​​stub, por exemplo, gitque provavelmente não deseja executar às cegas.
Bob Aman
5

Sei que estou sendo um pouco necromante aqui, mas me deparei com essa pergunta e a solução aceita não funcionou para mim em todos os casos. Achei que seria útil enviar de qualquer maneira. Em particular, a detecção do modo "executável" e o requisito de fornecer a extensão do arquivo. Além disso, o python3.3 shutil.which(usa PATHEXT) e o python2.4 + distutils.spawn.find_executable(apenas tenta adicionar '.exe') funcionam apenas em um subconjunto de casos.

Então, escrevi uma versão "super" (com base na resposta aceita e na PATHEXTsugestão de Suraj). Esta versão whichfaz a tarefa um pouco mais detalhadamente e tenta primeiro uma série de técnicas de "fase larga" em primeiro lugar e, eventualmente, tenta pesquisas mais refinadas no PATHespaço:

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

O uso fica assim:

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

A solução aceita não funcionou para mim neste caso, uma vez que havia arquivos como meld.1, meld.ico, meld.doap, etc, também no diretório, um dos quais foram devolvidos em vez (presumivelmente desde lexicographically primeiro) porque o teste executável na resposta aceita foi incompleta e dando falso-positivo.

Preet Kukreti
fonte
2

Encontrei algo no StackOverflow que resolveu o problema para mim. Isso funciona desde que o executável tenha uma opção (como --help ou --version) que produz algo e retorna um status de saída igual a zero. Consulte Suprimir saída em chamadas do Python para executáveis - o "resultado" no final do trecho de código nesta resposta será zero se o executável estiver no caminho, caso contrário, é mais provável que seja 1.

Somesh
fonte
2

Isso parece bastante simples e funciona tanto em python 2 quanto em 3

try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')
jaap
fonte
Desculpe Jaap, mas esta solução só funciona quando o executável não chama um código de saída 1 se for chamado incorretamente. Então, por exemplo, funcionará para "dir" e "ls", mas se você executar contra algo que exija configuração, ele será interrompido mesmo que o executável esteja lá.
Spedge
1
O que você quer dizer exatamente com "exigir configuração"? Por si só, 'what', na verdade, não executa nada, mas apenas verifica o PATH quanto à existência de um executável com esse nome (man what).
26615
1
Ohh, então você está usando "what" para encontrar o executável. Então, isso só funciona para Linux / Unix?
Spedge
1
Use command -v executableou type executablepara ser universal. Há casos em que nos Macs não retorna os resultados esperados.
RJ
1

Uma pergunta importante é " Por que você precisa testar se existe um executável?" Talvez você não? ;-)

Recentemente, eu precisava dessa funcionalidade para iniciar o visualizador de arquivos PNG. Eu queria interagir com alguns espectadores predefinidos e executar o primeiro que existe. Felizmente, me deparei os.startfile. É muito melhor! Simples, portátil e usa o visualizador padrão no sistema:

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

Atualização: Eu estava errado em os.startfileser portátil ... É apenas o Windows. No Mac, você deve executar o opencomando. E xdg_openno Unix. Há um problema de Python ao adicionar suporte para Mac e Unix os.startfile.

cinza
fonte
1

Você pode tentar a biblioteca externa chamada "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
jung rhew
fonte
1

Adicionado suporte ao 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
wukong
fonte
0

você pode dizer se existe um arquivo com o módulo os. um executável em particular parece bastante portável, considerando que muitas coisas são executáveis ​​no nix que não estão no Windows e vice-versa.

Dustin Getz
fonte
0

Parece que a escolha óbvia é "what", analisando os resultados via popen, mas você pode simular isso usando a classe os. No pseudopython, ficaria assim:

for each element r in path:
    for each file f in directory p:
        if f is executable:
           return True
Charlie Martin
fonte
Eu teria cuidado ao executar um comando "what" usando os.exec ou algo parecido. Não apenas é frequentemente lento (se o desempenho é algum tipo de preocupação), mas se você estiver usando uma variável como parte de sua cadeia de exec, a segurança se tornará uma preocupação. Alguém poderia se infiltrar em um "rm -rf /".
Parappa
1
Qual, como estaríamos usando a função os.popen para executar um comando criado pelo programa, na verdade não se aplica, não?
Charlie Martin
2
Obrigado, mas não tenho certeza se 'qual' existe no Windows e nos gostos. Eu queria essencialmente saber se existe algo chique na lib padrão
Piotr Lesnicki 18/12/08
Nas instalações padrão do Windows, ainda não há whichcomando; existe uma versão do UnxUtils, mas você deve saber / especificar a extensão, caso contrário, o programa não será encontrado.
Tobias
0

Então, basicamente, você deseja encontrar um arquivo no sistema de arquivos montado (não necessariamente nos diretórios PATH) e verificar se é executável. Isso se traduz no seguinte plano:

  • enumere todos os arquivos em sistemas de arquivos montados localmente
  • corresponder resultados com padrão de nome
  • para cada arquivo encontrado, verifique se é executável

Eu diria que fazer isso de maneira portátil exigirá muito poder e tempo de computação. É realmente o que você precisa?

zgoda
fonte
0

Existe um script what.py em uma distribuição padrão do Python (por exemplo, no Windows '\PythonXX\Tools\Scripts\which.py').

EDIT: which.pydepende, lsportanto, não é multiplataforma.

jfs
fonte
0

Nenhum dos exemplos anteriores funciona em todas as plataformas. Geralmente eles não funcionam no Windows, porque você pode executar sem a extensão do arquivo e registrar uma nova extensão. Por exemplo, no Windows, se o python estiver bem instalado, basta executar 'file.py' e ele funcionará.

A única solução válida e portátil que eu tinha era executar o comando e ver o código de erro. Qualquer executável decente deve ter um conjunto de parâmetros de chamada que não farão nada.

sorin
fonte
-3

Usando a biblioteca de malhas 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
frodopwns
fonte
2
Esta é uma sugestão muito ruim . Você está literalmente fazendo com que o programa dependa da biblioteca de execução remota para gerar um programa local (o que o Python stdlib pode fazer com facilidade) e, além disso, depende do which(1)que não está presente em todos os sistemas.
Michał Górny