Bagaimana saya bisa memiliki lebih dari satu kemungkinan dalam baris shebang skrip?

16

Saya berada dalam sedikit situasi yang menarik di mana saya memiliki skrip Python yang secara teoritis dapat dijalankan oleh berbagai pengguna dengan berbagai lingkungan (dan PATH) dan pada berbagai sistem Linux. Saya ingin skrip ini dapat dieksekusi pada sebanyak mungkin dari ini tanpa batasan buatan. Berikut ini beberapa pengaturan yang diketahui:

  • Python 2.6 adalah versi sistem Python, jadi python, python2, dan python2.6 semuanya ada di / usr / bin (dan setara).
  • Python 2.6 adalah versi sistem Python, seperti di atas, tetapi Python 2.7 diinstal di sampingnya sebagai python2.7.
  • Python 2.4 adalah versi sistem Python, yang tidak didukung skrip saya. Di / usr / bin kita memiliki python, python2, dan python2.4 yang setara, dan python2.5, yang didukung skrip.

Saya ingin menjalankan skrip python yang dapat dieksekusi yang sama pada ketiganya. Akan lebih baik jika mencoba menggunakan / usr/bin/python2.7 terlebih dahulu, jika ada, kemudian kembali ke /usr/bin/python2.6, kemudian kembali ke /usr/bin/python2.5, lalu cukup kesalahan jika tidak ada yang hadir. Saya tidak terlalu menutup telepon dengan menggunakan 2.x terbaru, asalkan bisa menemukan salah satu penerjemah yang tepat jika ada.

Kecenderungan pertama saya adalah mengubah garis shebang dari:

#!/usr/bin/python

untuk

#!/usr/bin/python2.[5-7]

karena ini berfungsi dengan baik di bash. Tetapi menjalankan skrip memberi:

/usr/bin/python2.[5-7]: bad interpreter: No such file or directory

Oke, jadi saya coba yang berikut ini, yang juga berfungsi di bash:

#!/bin/bash -c /usr/bin/python2.[5-7]

Tetapi sekali lagi, ini gagal dengan:

/bin/bash: - : invalid option

Oke, jelas saya hanya bisa menulis skrip shell terpisah yang menemukan penerjemah yang benar dan menjalankan skrip python menggunakan penerjemah apa pun yang ditemukannya. Saya hanya merasa kesulitan untuk mendistribusikan dua file di mana orang harus mencukupi selama itu dijalankan dengan interpreter python 2 yang paling up-to-date diinstal. Meminta orang untuk memohon penerjemah secara eksplisit (misalnya, $ python2.5 script.py) bukanlah suatu pilihan. Mengandalkan PATH pengguna yang diatur dengan cara tertentu juga bukan pilihan.

Edit:

Pengecekan versi dalam skrip Python tidak akan berfungsi karena saya menggunakan pernyataan "with" yang ada pada Python 2.6 (dan dapat digunakan dalam 2.5 dengan from __future__ import with_statement). Ini menyebabkan skrip gagal segera dengan SyntaxError pengguna-tidak ramah, dan mencegah saya dari memiliki kesempatan untuk memeriksa versi terlebih dahulu dan memancarkan kesalahan yang sesuai.

Contoh: (coba ini dengan penerjemah Python kurang dari 2,6)

#!/usr/bin/env python

import sys

print "You'll never see this!"
sys.exit()

with open('/dev/null', 'w') as out:
    out.write('something')
pengguna108471
sumber
Bukan yang Anda inginkan, untuk itu komentar. Tetapi Anda dapat menggunakannya import sys; sys.version_info()untuk memeriksa apakah pengguna memiliki versi python yang diperlukan.
Bernhard
2
@ Bernhard Ya ini benar, tetapi pada saat itu sudah terlambat untuk melakukan apa-apa. Untuk situasi ketiga yang saya sebutkan di atas menjalankan skrip secara langsung (yaitu, ./script.py) akan menyebabkan python2.4 untuk mengeksekusinya, yang akan menyebabkan kode Anda mendeteksi bahwa itu adalah versi yang salah (dan berhenti, mungkin). Tapi ada python2.5 yang sangat bagus yang bisa digunakan sebagai penerjemah!
user108471
2
Gunakan skrip pembungkus untuk mencari tahu apakah ada python yang sesuai dan execjika demikian, cetak kesalahan.
Kevin
1
Jadi lakukan hal pertama di file yang sama.
Kevin
9
@ user108471: Anda mengasumsikan garis shebang ditangani oleh bash. Bukan, ini panggilan sistem ( execve). Argumennya adalah string literal, tidak ada globbing, tidak ada regexps. Itu dia. Bahkan jika arg pertama adalah "/ bin / bash" dan opsi kedua ("-c ...") opsi tersebut tidak diuraikan oleh shell. Mereka tidak ditangani ke bash executable, itulah sebabnya Anda mendapatkan kesalahan tersebut. Plus, shebang hanya berfungsi jika sudah di awal. Jadi Anda kurang beruntung di sini, saya takut (pendek naskah yang menemukan juru bahasa python dan memberinya sebuah dokumen DI SINI, yang terdengar seperti kekacauan yang mengerikan).
goldilocks

Jawaban:

13

Saya bukan ahli, tapi saya percaya bahwa Anda tidak harus menentukan versi python yang tepat untuk digunakan dan meninggalkan pilihan itu ke sistem / pengguna.

Anda juga harus menggunakan itu alih-alih jalur hardcoding ke python dalam skrip:

#!/usr/bin/env python

atau

#!/usr/bin/env python3 (or python2)

Hal ini direkomendasikan oleh Python doc di semua versi:

Pilihan yang baik biasanya

#!/usr/bin/env python

yang mencari juru bahasa Python di seluruh PATH. Namun, beberapa Unices mungkin tidak memiliki perintah env, jadi Anda mungkin perlu hardcode / usr / bin / python sebagai jalur juru bahasa.

Dalam berbagai distribusi, Python dapat diinstal di tempat yang berbeda, jadi envakan mencarinya di PATH. Itu harus tersedia di semua distribusi Linux utama dan dari apa yang saya lihat di FreeBSD.

Skrip harus dijalankan dengan versi Python yang ada di PATH Anda dan yang dipilih oleh distribusi Anda *.

Jika skrip Anda kompatibel dengan semua versi Python kecuali 2.4, Anda harus mengeceknya jika dijalankan di Python 2.4 dan mencetak beberapa info dan keluar.

Lebih banyak untuk dibaca

  • Di sini Anda dapat menemukan contoh di tempat-tempat apa Python mungkin diinstal di sistem yang berbeda.
  • Di sini Anda dapat menemukan beberapa kelebihan dan kekurangan untuk digunakan env.
  • Di sini Anda dapat menemukan contoh manipulasi PATH dan hasil yang berbeda.

Catatan kaki

* Di Gentoo ada alat bernama eselect. Dengan menggunakannya, Anda dapat menetapkan versi default aplikasi yang berbeda (termasuk Python) sebagai default:

$ eselect python list
Available Python interpreters:
  [1]   python2.6
  [2]   python2.7 *
  [3]   python3.2
$ sudo eselect python set 1
$ eselect python list
Available Python interpreters:
  [1]   python2.6 *
  [2]   python2.7
  [3]   python3.2
pbm
sumber
2
Saya menghargai bahwa apa yang ingin saya lakukan bertentangan dengan apa yang dianggap praktik yang baik. Apa yang Anda posting sepenuhnya masuk akal, tetapi pada saat yang sama bukan itu yang saya minta. Saya tidak ingin pengguna saya harus secara eksplisit menunjukkan skrip saya pada versi Python yang sesuai ketika sangat mungkin untuk mendeteksi versi Python yang sesuai dalam semua situasi yang saya pedulikan.
user108471
1
Silakan lihat pembaruan saya untuk alasan mengapa saya tidak dapat "memeriksa bagian dalamnya jika dijalankan dengan Python 2.4 dan mencetak beberapa info dan keluar."
user108471
Kamu benar. Saya baru saja menemukan pertanyaan ini di SO dan sekarang saya dapat melihat bahwa tidak ada opsi untuk melakukan itu jika Anda ingin hanya memiliki satu file ...
pbm
9

Berdasarkan beberapa ide dari beberapa komentar, saya berhasil meretas sebuah hack yang benar-benar jelek yang tampaknya berhasil. Script menjadi skrip bash yang membungkus skrip Python dan meneruskannya ke juru bahasa Python melalui "dokumen di sini".

Pada awalnya:

#!/bin/bash

''':'
vers=( /usr/bin/python2.[5-7] )
latest="${vers[$((${#vers[@]} - 1))]}"
if !(ls $latest &>/dev/null); then
    echo "ERROR: Python versions < 2.5 not supported"
    exit 1
fi
cat <<'# EOF' | exec $latest - "$@"
''' #'''

Kode Python ada di sini. Kemudian di bagian paling akhir:

# EOF

Saat pengguna menjalankan skrip, versi Python terbaru antara 2.5 dan 2.7 digunakan untuk menafsirkan sisa skrip sebagai dokumen di sini.

Penjelasan tentang beberapa shenanigans:

Hal-hal triple-quote yang saya tambahkan juga memungkinkan skrip yang sama ini diimpor sebagai modul Python (yang saya gunakan untuk tujuan pengujian). Ketika diimpor oleh Python, semua yang ada di antara triple-single-quote pertama dan kedua ditafsirkan sebagai string tingkat modul, dan triple-single-quote ketiga dikomentari. Sisanya adalah Python biasa.

Ketika dijalankan secara langsung (sebagai skrip bash sekarang), dua tanda kutip tunggal menjadi string kosong, dan tanda kutip tunggal ketiga membentuk string lain dengan tanda kutip tunggal keempat, yang hanya berisi tanda titik dua. String ini ditafsirkan oleh Bash sebagai no-op. Semua yang lain adalah sintaks Bash untuk globbing binari Python di / usr / bin, memilih yang terakhir, dan menjalankan exec, melewati sisa file sebagai dokumen di sini. Dokumen di sini dimulai dengan kutipan tiga-tunggal Python yang hanya berisi tanda hash / pound / octothorpe. Sisa skrip kemudian ditafsirkan sebagai normal sampai baris yang membaca '# EOF' mengakhiri dokumen di sini.

Saya merasa ini sesat, jadi saya berharap seseorang memiliki solusi yang lebih baik.

pengguna108471
sumber
Mungkin tidak terlalu jahat;) +1
goldilocks
Kerugian dari ini adalah bahwa itu akan mengacaukan pewarnaan sintaks pada kebanyakan editor
Lie Ryan
@ LieRyan Itu tergantung. Skrip saya menggunakan ekstensi nama file .py, yang disukai sebagian besar editor teks saat memilih sintaks yang akan digunakan untuk pewarnaan. Hipotetis, jika saya harus mengubah nama ini tanpa ekstensi py, saya bisa menggunakan modeline untuk petunjuk sintaks yang benar (untuk pengguna Vim setidaknya) dengan sesuatu seperti: # ft=python.
user108471
7

Garis shebang hanya dapat menentukan jalur tetap ke juru bahasa. Ada #!/usr/bin/envtrik untuk mencari penerjemah di PATHtetapi itu saja. Jika Anda menginginkan lebih banyak kecanggihan, Anda perlu menulis beberapa kode shell wrapper.

Solusi yang paling jelas adalah menulis skrip wrapper. Panggil skrip python foo.realdan buat skrip wrapper foo:

#!/bin/sh
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi

Jika Anda ingin meletakkan semuanya dalam satu file, Anda sering dapat menjadikannya polyglot yang dimulai dengan #!/bin/shbaris (jadi akan dieksekusi oleh shell) tetapi juga merupakan skrip yang valid dalam bahasa lain. Bergantung pada bahasanya, poliglot mungkin tidak mungkin (jika #!menyebabkan kesalahan sintaksis, misalnya). Dalam Python, itu tidak terlalu sulit.

#!/bin/sh
''':'
if type python2 >/dev/null 2>/dev/null; then
  exec python2 "$0.real" "$@"
else
  exec python "$0.real" "$@"
fi
'''
# real Python script starts here
def …

(Seluruh teks antara '''dan '''merupakan string Python di tingkat atas, yang tidak memiliki efek. Untuk shell, baris kedua adalah ''':'yang setelah pengupasan tanda kutip adalah perintah no-op :.)

Gilles 'SO- berhenti menjadi jahat'
sumber
Solusi kedua bagus karena tidak perlu menambahkan # EOFpada akhir seperti pada jawaban ini . Pendekatan Anda pada dasarnya sama dengan yang diuraikan di sini .
sschuberth
6

Karena persyaratan Anda menyatakan daftar binari yang diketahui, Anda bisa melakukannya dengan Python dengan yang berikut ini. Itu tidak akan berfungsi melewati satu digit minor / versi utama dari Python tapi saya tidak melihat itu terjadi dalam waktu dekat.

Menjalankan versi tertinggi yang terletak di disk dari daftar ular piton berversi yang diperintahkan dan bertambah, jika versi yang ditandai pada biner lebih tinggi daripada versi eksekusi python saat ini. "Daftar peningkatan versi yang dipesan" menjadi bit penting untuk kode ini.

#!/usr/bin/env python
import os, sys

pythons = [ '/usr/bin/python2.3','/usr/bin/python2.4', '/usr/bin/python2.5', '/usr/bin/python2.6', '/usr/bin/python2.7' ]
py = list(filter( os.path.isfile, pythons ))
if py:
  py = py.pop()
  thepy = int( py[-3:-2] + py[-1:] )
  mypy  = int( ''.join( map(str, sys.version_info[0:2]) ) )
  if thepy > mypy:
    print("moving versions to "+py)
    args = sys.argv
    args.insert( 0, sys.argv[0] )
    os.execv( py, args )

print("do normal stuff")

Permintaan maaf untuk ular piton saya yang gatal

Mat
sumber
bukankah itu akan terus berjalan setelah mengeksekusi? jadi hal-hal normal akan dieksekusi dua kali?
Janus Troelsen
1
execv menggantikan program yang sedang dijalankan dengan gambar program yang baru dimuat
Matt
Ini terlihat seperti solusi hebat yang terasa jauh lebih buruk dari apa yang saya pikirkan. Saya harus mencoba ini untuk melihat apakah itu berfungsi untuk tujuan ini.
user108471
Saran ini hampir berhasil untuk apa yang saya butuhkan. Satu-satunya kelemahan adalah sesuatu yang awalnya tidak saya sebutkan: untuk mendukung Python 2.5, saya menggunakan from __future__ import with_statement, yang harus menjadi hal pertama dalam skrip Python. Saya kira Anda tidak tahu cara melakukan tindakan itu ketika memulai juru bahasa baru?
user108471
yang Anda yakin perlu menjadi sangat hal pertama? atau tepat sebelum Anda mencoba dan menggunakan yang withseperti impor biasa ?. Apakah if mypy == 25: from __future__ import with_statementpekerjaan tambahan tepat sebelum 'barang normal'? Anda mungkin tidak memerlukan if, jika Anda tidak mendukung 2.4.
Matt
0

Anda dapat menulis skrip bash kecil yang memeriksa executable phython yang tersedia dan menyebutnya dengan skrip sebagai parameter. Anda kemudian dapat membuat skrip ini sebagai target garis shebang:

#!/my/python/search/script

Dan skrip ini cukup (setelah pencarian):

"$python_path" "$1"

Saya tidak yakin apakah kernel akan menerima tipuan skrip ini, tetapi saya memeriksa dan berfungsi.

Edit 1

Untuk menjadikan ketidakpahaman yang memalukan ini, proposal yang bagus akhirnya:

Dimungkinkan untuk menggabungkan kedua skrip dalam satu file. Anda cukup menulis skrip python sebagai dokumen di sini di skrip bash (jika Anda mengubah skrip python, Anda hanya perlu menyalin skrip tersebut lagi). Entah Anda membuat file sementara dalam eg / tmp atau (jika python mendukungnya, saya tidak tahu) Anda memberikan skrip sebagai input ke penerjemah:

# do the search here and then
# either
cat >"tmpfile" <<"EOF" # quoting EOF is important so that bash leaves the python code alone
# here is the python script
EOF
"$python_path" "tmpfile"
# or
"$python_path" <<"EOF"
# here is the python script
EOF
Hauke ​​Laging
sumber
Ini kurang lebih solusi yang sudah dinyatakan dalam paragraf terakhir dari qeustion!
Bernhard
Pintar, tetapi membutuhkan skrip ajaib ini untuk diinstal di suatu tempat di setiap sistem.
user108471
@Bernhard Oooops, tertangkap. Di masa depan saya akan membaca sampai akhir. Sebagai kompensasi saya akan memperbaikinya menjadi solusi satu file.
Hauke ​​Laging
@ user108471 Script ajaib dapat berisi sesuatu seperti ini: $(ls /usr/bin/python?.? | tail -n1 )tapi saya tidak berhasil menggunakan ini secara cerdik dalam shebang.
Bernhard
@Bernhard Anda ingin melakukan pencarian di dalam garis shebang? IIRC kernel tidak peduli tentang mengutip di baris shebang. Kalau tidak (jika ini telah berubah sementara) seseorang dapat melakukan sesuatu seperti `#! / Bin / bash -c do_search_here_without_whitespace ...; exec $ python" $ 1 "Tapi bagaimana melakukannya tanpa spasi putih?
Hauke ​​Laging