Uji apakah executable ada di Python?

297

Dalam Python, apakah ada cara portabel dan sederhana untuk menguji apakah ada program yang dapat dieksekusi?

Secara sederhana saya maksudkan sesuatu seperti whichperintah yang akan sempurna. Saya tidak ingin mencari PATH secara manual atau sesuatu yang melibatkan mencoba untuk mengeksekusinya dengan Popen& al dan melihat apakah itu gagal (itulah yang saya lakukan sekarang, tetapi bayangkan itu launchmissiles)

Piotr Lesnicki
sumber
4
Apa yang salah dengan mencari variabel lingkungan PATH? Menurut Anda apa perintah UNIX 'yang' lakukan?
Jay
1
Apakah script mana .py dari stdlib dengan cara sederhana?
jfs
@ JF - skrip which.py ​​termasuk. dengan Python tergantung pada 'ls' dan beberapa komentar lain menunjukkan bahwa Piotr sedang mencari jawaban lintas platform.
Jay
@ Jay: Terima kasih atas komentarnya. Saya memiliki coreutils yang diinstal pada Windows, jadi saya tidak melihat itu which.py ​​tidak spesifik-unix.
jfs
Ada juga which, modul pihak ketiga: code.activestate.com/pypm/which
Sridhar Ratnakumar

Jawaban:

321

Cara termudah yang bisa saya pikirkan:

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

Sunting : Sampel kode yang diperbarui untuk memasukkan logika untuk menangani kasus di mana argumen yang disediakan sudah path lengkap ke executable, yaitu "yang / bin / ls". Ini meniru perilaku perintah 'yang' UNIX.

Sunting : Diperbarui untuk menggunakan os.path.isfile () alih-alih os.path.exists () per komentar.

Sunting : path.strip('"')sepertinya hal yang salah untuk dilakukan di sini. Baik Windows maupun POSIX tidak muncul untuk mendorong item PATH yang dikutip.

Jay
sumber
Terima kasih Jay, saya menerima jawaban Anda, meskipun bagi saya itu menjawab pertanyaan saya dengan negatif. Tidak ada fungsi seperti itu di libs, saya hanya perlu menulisnya (saya akui formulasi saya tidak cukup jelas dalam kenyataan bahwa saya tahu apa yang dilakukannya).
Piotr Lesnicki
1
Jay, jika kamu menyelesaikan jawabanmu sesuai dengan milikku (untuk melengkapi 'w') maka aku dapat menghapus jawaban ku
Piotr Lesnicki
2
Untuk beberapa OS, Anda mungkin perlu menambahkan ekstensi yang dapat dieksekusi. Sebagai contoh, pada Ubuntu saya dapat menulis yang ("scp") tetapi pada Windows, saya harus menulis yang ("scp.exe").
waffleman
13
Saya sarankan mengubah "os.path.exists" menjadi "os.path.isfile". Kalau tidak di Unix ini mungkin cocok dengan direktori dengan set + x bit. Saya juga merasa berguna untuk menambahkan ini ke bagian atas fungsi: import sys; jika sys.platform == "win32" dan bukan program.endswith (". exe"): program + = ".exe". Dengan cara ini di Windows Anda bisa merujuk ke "calc" atau "calc.exe", seperti yang Anda bisa di jendela cmd.
Kevin Ivarsen
1
@KevinIvarsen Sebuah pilihan yang lebih baik akan perulangan melalui nilai-nilai dari PATHEXTvar env karena commandadalah sebagai berlaku sebagai command.comseperti scriptvsscript.bat
Lekensteyn
325

Saya tahu ini adalah pertanyaan kuno, tetapi Anda dapat menggunakannya distutils.spawn.find_executable. Ini telah didokumentasikan sejak python 2.4 dan telah ada sejak python 1.6.

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

Juga, Python 3.3 sekarang menawarkan shutil.which().

Nathan Binkert
sumber
7
Aktif win32, distutils.spawn.find_executableimplementasi hanya mencari .exedaripada menggunakan daftar ekstensi untuk mencari yang ditetapkan %PATHEXT%. Itu tidak bagus, tetapi mungkin berhasil untuk semua kasus yang dibutuhkan seseorang.
rakslice
7
contoh penggunaan:from distutils import spawn php_path = spawn.find_executable("php")
codefreak
6
Rupanya distutils.spawntidak tersedia andal: dengan Sistem saya menginstal (/ usr / bin / python) dari Python 2.7.6 pada OS X 10.10, saya mendapatkan AttributeError: 'module' object has no attribute 'spawn':, meskipun anehnya itu bekerja pada mesin yang sama dengan versi Python yang sama, tetapi dari instalasi virtualenv.
Josh Kupershmidt
8
@ JoshKupershmidt, pastikan untuk import distutils.spawn, atau ikuti from distutils import spawnsintaks daripada hanya import distutils. Kalau tidak, itu mungkin tidak dapat diakses dan Anda akan mendapatkan di atas AttributeErrorbahkan jika itu ada.
John St. John
39

Untuk python 3.2 dan sebelumnya:

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

Ini adalah satu-baris Jawaban Jay , Juga di sini sebagai fungsi 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')

Atau terakhir, indentasi sebagai fungsi:

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

Untuk python 3.3 dan yang lebih baru:

import shutil

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

Sebagai pelaut Jan-Philip Gehrcke Jawaban :

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

Sebagai def:

def cmd_exists(cmd):
    return shutil.which(cmd) is not None
ThorSummoner
sumber
1
versi "indentasi sebagai fungsi" menggunakan variabel xyang seharusnyacmd
0x89
Anda juga harus menambahkan tes untuk melihat apakah os.path.join(path, cmd)file, bukan? Lagipula, direktori juga dapat memiliki set bit yang dapat dieksekusi ...
MestreLion
@MestreLion Kedengarannya seperti kasus yang memungkinkan, maukah Anda mengonfirmasi perilaku ini dan memperbarui jawaban ini? Saya senang mengubah pos ini ke wiki komunitas jika itu membantu.
ThorSummoner
1
@ Tumormoner: Saya sudah mengkonfirmasi, dan memang membutuhkan tes untuk file. Tes sederhana: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
Menambahkan sederhana and os.path.isfile(...)ke tempat yang sesuai sudah cukup untuk memperbaikinya
MestreLion
19

Hanya ingat untuk menentukan ekstensi file di windows. Kalau tidak, Anda harus menulis banyak rumit is_exeuntuk windows menggunakan PATHEXTvariabel lingkungan. Anda mungkin hanya ingin menggunakan FindPath .

OTOH, mengapa Anda repot-repot mencari yang dapat dieksekusi? Sistem operasi akan melakukannya untuk Anda sebagai bagian dari popenpanggilan & akan memunculkan pengecualian jika executable tidak ditemukan. Yang perlu Anda lakukan adalah menangkap pengecualian yang benar untuk OS yang diberikan. Perhatikan bahwa pada Windows, subprocess.Popen(exe, shell=True)akan gagal secara diam-diam jika exetidak ditemukan.


Menggabungkan PATHEXTke dalam implementasi di atas which(dalam jawaban 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
sumber
1
Itu memperbaiki bug dalam jawaban yang diterima, merasa jawaban ini seharusnya ada di atas.
NiTe Luo
penggunaan cerdas yielddalam ext_candidates, memberi saya pemahaman yang lebih baik tentang cara kerja kata kunci itu
Grant Humphries
15

Untuk platform * nix (Linux dan OS X)

Ini sepertinya bekerja untuk saya:

Diedit untuk bekerja di Linux, terima kasih kepada Mestreion

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

Apa yang kami lakukan di sini adalah menggunakan perintah builtin typedan memeriksa kode keluar. Jika tidak ada perintah seperti itu, typeakan keluar dengan 1 (atau kode status bukan nol).

Bit tentang stdout dan stderr hanya untuk membungkam output dari typeperintah, karena kami hanya tertarik pada kode status keluar.

Contoh penggunaan:

>>> 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
sumber
2
Apakah Anda yakin ini berhasil? Itu pendekatan yang sangat bagus, tetapi typeshell builtin, bukan file yang dapat dieksekusi, jadi subprocess.call()gagal di sini.
MestreLion
1
Sudahkah Anda mencobanya atau hanya berteori? Ini bekerja di mac saya.
Hasen
Saya sudah mencobanya di Ubuntu 12.04, ia melempar OSError: [Errno 2] No such file or directory. Mungkin di Mac typeadalah perintah sebenarnya
MestreLion
2
Setelah BANYAK pengujian, saya telah menemukan cara memperbaikinya: tambah shell=Truedan ganti ["type", cmd]untuk"type " + cmd
MestreLion
4
Perhatian: pastikan bahwa variabel "cmd" berisi data yang valid. Jika itu berasal dari sumber eksternal, orang jahat bisa memberi Anda "ls; rm -rf /". Saya pikir solusi in-python (tanpa subprocess) jauh lebih baik. Poin berikutnya: Jika Anda sering memanggil metode ini, solusi subproses jauh lebih lambat, karena banyak proses perlu ditelurkan.
guettli
7

Lihat modul os.path untuk mengetahui beberapa fungsi berguna pada nama path. Untuk memeriksa apakah file yang ada dapat dieksekusi, gunakan os.access (path, mode) , dengan mode os.X_OK.

os.X_OK

Nilai untuk dimasukkan dalam parameter mode akses () untuk menentukan apakah jalur dapat dieksekusi.

EDIT:which() Implementasi yang disarankan tidak memiliki satu petunjuk - gunakan os.path.join()untuk membangun nama file lengkap.

Gimel
sumber
Terima kasih, gimel, jadi pada dasarnya saya punya jawaban: tidak ada fungsi seperti itu, saya harus melakukannya secara manual.
Piotr Lesnicki
Jangan gunakan os.access. fungsi akses dirancang untuk program suid.
Changming Sun
6

Atas dasar itu lebih mudah untuk meminta maaf daripada izin saya hanya akan mencoba menggunakannya dan menangkap kesalahan (OSError dalam hal ini - saya memeriksa file tidak ada dan file tidak dapat dieksekusi dan mereka berdua memberikan OSError).

Ini membantu jika executable memiliki sesuatu seperti --versionflag yang merupakan no-op cepat.

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

Ini bukan solusi umum, tetapi akan menjadi cara termudah untuk banyak kasus penggunaan - kasus di mana kode perlu mencari satu executable terkenal.

Hamish Downer
sumber
3
Terlalu berbahaya bahkan untuk memanggil --versionprogram bernama launchmissiles!
xApple
1
+1, saya menyukai pendekatan ini. EAFP adalah aturan Python emas. Kecuali mungkin untuk pengaturan UI, mengapa Anda ingin tahu jika launchmissiesada kecuali Anda ingin meluncurkan rudal? Lebih baik untuk mengeksekusinya dan bertindak berdasarkan status keluar / pengecualian
MestreLion
Masalah dengan metode ini adalah output dicetak ke konsol. Jika Anda menggunakan pipa dan shell = Benar, maka OSError tidak pernah dinaikkan
Nick Humrich
Pada macOS Anda juga memiliki executable rintisan untuk misalnya gitbahwa Anda mungkin tidak ingin menjalankan secara membabi buta.
Bob Aman
5

Saya tahu bahwa saya menjadi sedikit ahli nujum di sini, tetapi saya menemukan pertanyaan ini dan solusi yang diterima tidak bekerja untuk saya untuk semua kasus. Secara khusus, deteksi mode "executable", dan persyaratan untuk memasok ekstensi file. Lebih jauh lagi, baik python3.3's shutil.which(uses PATHEXT) dan python2.4 + distutils.spawn.find_executable(hanya mencoba menambahkan '.exe') hanya berfungsi dalam subset kasus.

Jadi saya menulis versi "super" (berdasarkan jawaban yang diterima, dan PATHEXTsaran dari Suraj). Versi whichtugas ini sedikit lebih teliti, dan mencoba serangkaian teknik "luas-lebar" pertama, dan akhirnya mencoba pencarian yang lebih baik atas PATHruang:

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

Penggunaannya terlihat seperti ini:

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

Solusi yang diterima tidak bekerja untuk saya dalam kasus ini, karena ada file seperti meld.1, meld.ico, meld.doap, dll juga di direktori, salah satunya dikembalikan bukan (mungkin karena leksikografi pertama) karena tes dieksekusi dalam jawaban yang diterima tidak lengkap dan memberikan positif palsu.

Preet Kukreti
sumber
2

Saya menemukan sesuatu di StackOverflow yang memecahkan masalah bagi saya. Ini berfungsi asalkan executable memiliki opsi (seperti --help atau --version) yang menampilkan sesuatu dan mengembalikan status keluar nol. Lihat Menekan keluaran dalam panggilan Python ke executable - "hasil" di akhir cuplikan kode dalam jawaban ini akan menjadi nol jika dieksekusi di jalur, kalau tidak kemungkinan besar adalah 1.

Somesh
sumber
2

Ini tampaknya cukup sederhana dan berfungsi baik di python 2 dan 3

try: subprocess.check_output('which executable',shell=True)
except: sys.exit('ERROR: executable not found')
Jaap
sumber
Maaf Jaap, tetapi solusi ini hanya berfungsi ketika executable tidak memanggil kode keluar 1 jika itu disebut salah. Jadi, misalnya, ini akan bekerja untuk "dir" dan "ls", tetapi jika Anda mengeksekusi terhadap sesuatu yang memerlukan konfigurasi, itu akan rusak meskipun executable ada di sana.
Spedge
1
Apa maksud Anda sebenarnya dengan "memerlukan konfigurasi"? Dengan sendirinya 'yang' tidak benar-benar mengeksekusi apa pun tetapi hanya memeriksa PATH untuk keberadaan yang dapat dieksekusi dengan nama ini (man yang).
jaap
1
Ohh, jadi Anda menggunakan "yang" untuk menemukan executable. Jadi ini hanya berfungsi untuk Linux / Unix?
Spedge
1
Gunakan command -v executableatau type executableuntuk menjadi universal. Ada kasus di mana pada Mac tidak mengembalikan hasil yang diharapkan.
RJ
1

Sebuah pertanyaan penting adalah " Mengapa Anda perlu menguji apakah executable ada?" Mungkin tidak? ;-)

Baru-baru ini saya membutuhkan fungsi ini untuk meluncurkan viewer untuk file PNG. Saya ingin mengulangi beberapa pemirsa yang telah ditentukan dan menjalankan yang pertama yang ada. Untungnya, saya menemukan os.startfile. Jauh lebih baik! Sederhana, portabel dan menggunakan penampil default pada sistem:

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

Pembaruan: Saya salah tentang os.startfilemenjadi portabel ... Ini hanya Windows. Di Mac Anda harus menjalankan openperintah. Dan xdg_opendi Unix. Ada masalah Python tentang menambahkan dukungan Mac dan Unix untuk os.startfile.

Abu
sumber
1

Anda dapat mencoba lib eksternal yang disebut "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
sumber
1

Dukungan windows ditambahkan

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
sumber
0

Anda dapat mengetahui apakah ada file dengan modul os. executable pada khususnya tampaknya cukup tidak dapat dipercaya mengingat banyak hal yang dapat dieksekusi pada nix yang tidak ada di windows dan sebaliknya.

Dustin Getz
sumber
0

Tampaknya pilihan yang jelas adalah "yang", mem-parsing hasil melalui popen, tetapi Anda bisa mensimulasikannya menggunakan kelas os. Dalam pseudopython, akan terlihat seperti ini:

for each element r in path:
    for each file f in directory p:
        if f is executable:
           return True
Charlie Martin
sumber
Saya akan berhati-hati menjalankan perintah "yang" menggunakan os.exec atau sesuatu seperti itu. Tidak hanya sering lambat (jika kinerja adalah masalah), tetapi jika Anda menggunakan variabel sebagai bagian dari string exec Anda, keamanan menjadi perhatian. Seseorang bisa menyelinap di "rm -rf /".
Parappa
1
Yang mana, karena kita akan menggunakan fungsi os.popen untuk menjalankan perintah yang dibuat oleh program, tidak benar-benar berlaku, bukan?
Charlie Martin
2
Terima kasih, tapi saya tidak yakin apakah 'yang' ada di windows dan sejenisnya. Saya pada dasarnya ingin tahu apakah ada sesuatu yang mewah di lib standar
Piotr Lesnicki
Dalam instalasi Windows standar, masih belum ada whichperintah; ada versi UnxUtils, tetapi Anda harus tahu / tentukan ekstensi, jika tidak program tidak akan ditemukan.
Tobias
0

Jadi pada dasarnya Anda ingin menemukan file dalam sistem file yang terpasang (tidak harus hanya di direktori PATH saja) dan periksa apakah itu dapat dieksekusi. Ini berarti rencana berikut:

  • menghitung semua file dalam sistem file yang dipasang secara lokal
  • mencocokkan hasil dengan pola nama
  • untuk setiap file yang ditemukan periksa apakah itu dapat dieksekusi

Menurut saya, melakukan ini dengan cara yang portabel akan membutuhkan banyak daya komputasi dan waktu. Benarkah itu yang Anda butuhkan?

zgoda
sumber
0

Ada skrip which.py dalam distribusi Python standar (misalnya pada Windows'\PythonXX\Tools\Scripts\which.py' ).

EDIT: which.pytergantung pada lskarena itu bukan cross-platform.

jfs
sumber
0

Tidak ada contoh sebelumnya yang berfungsi pada semua platform. Biasanya mereka gagal bekerja di Windows karena Anda dapat mengeksekusi tanpa ekstensi file dan Anda dapat mendaftarkan ekstensi baru. Sebagai contoh pada Windows jika python terinstal dengan baik, itu cukup untuk menjalankan 'file.py' dan itu akan bekerja

Satu-satunya solusi yang valid dan portabel yang saya miliki adalah menjalankan perintah dan melihat kode kesalahan. Setiap executable yang layak harus memiliki seperangkat parameter panggilan yang tidak akan melakukan apa pun.

Sorin
sumber
-3

Menggunakan pustaka kain 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
sumber
2
Ini saran yang sangat buruk . Anda benar-benar membuat program bergantung pada pustaka eksekusi jarak jauh untuk menelurkan program lokal (yang dapat dilakukan dengan mudah oleh Python stdlib), dan selain itu, Anda bergantung pada which(1)yang tidak ada pada semua sistem.
Michał Górny