Pilih penerjemah setelah skrip mulai misalnya jika / lain di dalam hashbang

16

Apakah ada cara untuk secara dinamis memilih penerjemah yang menjalankan skrip? Saya memiliki skrip yang saya jalankan di dua sistem yang berbeda, dan juru bahasa yang ingin saya gunakan terletak di lokasi yang berbeda di kedua sistem. Apa yang saya akhirnya harus mengubah baris hashbang setiap kali saya beralih. Saya ingin melakukan sesuatu yang setara logis dengan ini (saya menyadari bahwa konstruksi yang tepat ini tidak mungkin):

if running on system A:
    #!/path/to/python/on/systemA
elif running on system B:
    #!/path/on/systemB

#Rest of script goes here

Atau yang lebih baik adalah ini, sehingga ia mencoba menggunakan juru bahasa pertama, dan jika tidak menemukannya menggunakan yang kedua:

try:
    #!/path/to/python/on/systemA
except: 
    #!path/on/systemB

#Rest of script goes here

Jelas, saya bisa menjalankannya sebagai /path/to/python/on/systemA myscript.py atau /path/on/systemB myscript.py tergantung di mana saya berada, tetapi saya benar-benar memiliki skrip pembungkus yang diluncurkan myscript.py, jadi saya ingin menentukan jalur ke juru bahasa python secara terprogram secara terprogram daripada dengan tangan.

dkv
sumber
3
meneruskan 'sisa skrip' sebagai file ke interpreter tanpa shebang, dan menggunakan ifkondisinya bukan pilihan bagi Anda? seperti,if something; then /bin/sh restofscript.sh elif...
mazs
Itu pilihan, saya juga mempertimbangkannya, tapi agak berantakan daripada yang saya inginkan. Karena logika dalam garis hashbang tidak mungkin, saya pikir saya memang akan menempuh rute itu.
dkv
Saya suka berbagai jawaban berbeda yang dihasilkan pertanyaan ini.
Oskar Skog

Jawaban:

27

Tidak, itu tidak akan berhasil. Dua karakter #!mutlak harus menjadi dua karakter pertama dalam file (bagaimana Anda menentukan apa yang menafsirkan pernyataan-if?). Ini merupakan "angka ajaib" yang exec()dideteksi oleh keluarga fungsi ketika mereka menentukan apakah file yang akan mereka eksekusi adalah skrip (yang membutuhkan penerjemah) atau file biner (yang tidak).

Format garis shebang cukup ketat. Perlu memiliki jalur absolut ke penerjemah dan paling banyak satu argumen untuk itu.

Yang dapat Anda lakukan adalah menggunakan env:

#!/usr/bin/env interpreter

Sekarang, jalan menuju envadalah biasanya /usr/bin/env , tapi secara teknis itu bukan jaminan.

Hal ini memungkinkan Anda untuk menyesuaikan PATHvariabel lingkungan pada setiap sistem sehingga interpreter(baik itu bash, pythonatau perlatau apa pun yang Anda miliki) ditemukan.

Kelemahan dari pendekatan ini adalah bahwa tidak mungkin untuk menyampaikan argumen kepada penerjemah dengan mudah.

Ini artinya

#!/usr/bin/env awk -f

dan

#!/usr/bin/env sed -f

tidak mungkin bekerja pada beberapa sistem.

Pendekatan lain yang jelas adalah dengan menggunakan autotool GNU (atau sistem templating sederhana) untuk menemukan penerjemah dan menempatkan jalur yang benar ke dalam file dalam ./configurelangkah, yang akan dijalankan saat menginstal skrip pada setiap sistem.

Seseorang juga dapat menggunakan skrip dengan penerjemah eksplisit, tapi itu jelas yang ingin Anda hindari:

$ sed -f script.sed
Kusalananda
sumber
Benar, saya menyadari bahwa #!perlu datang di awal, karena itu bukan shell yang memproses garis itu. Saya bertanya-tanya apakah ada cara untuk menempatkan logika di dalam baris hashbang yang akan setara dengan if / else. Saya juga berharap untuk tidak bermain-main dengan saya PATHtetapi saya kira itu adalah satu-satunya pilihan saya.
dkv
1
Saat Anda menggunakan #!/usr/bin/awk, Anda dapat memberikan satu argumen, sebagai #!/usr/bin/awk -f. Jika biner yang Anda tuju adalah env, argumennya adalah biner yang Anda minta envcari, seperti dalam #!/usr/bin/env awk.
DopeGhoti
2
@ Gdv Tidak. Ia menggunakan seorang juru bahasa dengan dua argumen, dan itu mungkin bekerja pada beberapa sistem, tetapi jelas tidak pada semua.
Kusalananda
3
@ Gdv di Linux ini berjalan /usr/bin/envdengan argumen tunggal awk -f.
ilkkachu
1
@ Kusalananda, tidak, itu intinya. Jika Anda memiliki skrip bernama foo.awkhashbang line #!/usr/bin/env awk -fdan menyebutnya dengan ./foo.awkitu, di Linux, yang envdilihat adalah dua parameter awk -fdan ./foo.awk. Ini benar-benar pergi mencari /usr/bin/awk -f(dll.) Dengan spasi.
ilkkachu
27

Anda selalu dapat membuat skrip wrapper untuk menemukan juru bahasa yang benar untuk program yang sebenarnya:

#!/bin/bash
if something ; then
    interpreter=this
    script=/some/path/to/program.real
    flags=()
else
    interpreter=that
    script=/other/path/to/program.real
    flags=(-x -y)
fi
exec "$interpreter" "${flags[@]}" "$script" "$@"

Simpan pembungkus di pengguna PATHsebagai programdan sisihkan program yang sebenarnya atau dengan nama lain.

Saya menggunakan #!/bin/bashhashbang karena flagsarray. Jika Anda tidak perlu menyimpan sejumlah flag atau semacamnya dan dapat melakukannya tanpa itu, skrip harus bekerja dengan baik #!/bin/sh.

ilkkachu
sumber
2
Saya telah melihat exec "$interpreter" "${flags[@]}" "$script" "$@"juga digunakan untuk menjaga proses tree cleaner. Itu juga menyebarkan kode keluar.
rrauenza
@rrauenza, ah ya, tentu saja dengan exec.
ilkkachu
1
Bukankah #!/bin/shlebih baik #!/bin/bash? Bahkan jika /bin/shsymlink ke shell yang berbeda, itu harus ada pada sebagian besar (jika tidak semua) * sistem nix, ditambah itu akan memaksa penulis skrip untuk membuat skrip portabel daripada jatuh ke bashism.
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy, heh, saya berpikir tentang menyebutkan itu sebelumnya, tetapi tidak, kalau begitu. Array yang digunakan flagsadalah fitur non-standar, tetapi cukup berguna untuk menyimpan sejumlah flag, jadi saya memutuskan untuk menyimpannya.
ilkkachu
Atau penggunaan / bin / sh dan hanya memanggil penerjemah langsung di setiap cabang: script=/what/ever; something && exec this "$script" "$@"; exec that "$script" -x -y "$@". Anda juga bisa menambahkan pengecekan error untuk kegagalan exec.
jrw32982 mendukung Monica
11

Anda juga dapat menulis polyglot (menggabungkan dua bahasa). / bin / sh dijamin ada.

Ini memiliki kelemahan dari kode jelek dan mungkin beberapa /bin/shs berpotensi menjadi bingung. Tetapi dapat digunakan ketika envtidak ada atau ada di tempat lain selain / usr / bin / env. Ini juga dapat digunakan jika Anda ingin melakukan beberapa pilihan yang cukup mewah.

Bagian pertama dari skrip menentukan penerjemah mana yang akan digunakan ketika dijalankan dengan / bin / sh sebagai penerjemah, tetapi diabaikan ketika dijalankan oleh penerjemah yang benar. Gunakan execuntuk mencegah shell dari menjalankan lebih dari bagian pertama.

Contoh python:

#!/bin/sh
'''
' 2>/dev/null
# Python thinks this is a string, docstring unfortunately.
# The shell has just tried running the <newline> program.
find_best_python ()
{
    for candidate in pypy3 pypy python3 python; do
        if [ -n "$(which $candidate)" ]; then
            echo $candidate
            return
        fi
    done
    echo "Can't find any Python" >/dev/stderr
    exit 1
}
interpreter="$(find_best_python)"   # Replace with something fancier.
# Run the rest of the script
exec "$interpreter" "$0" "$@"
'''
Oskar Skog
sumber
3
Saya pikir saya telah melihat salah satu dari ini sebelumnya, tetapi idenya masih sama buruknya ... Tapi, Anda mungkin ingin exec "$interpreter" "$0" "$@"mendapatkan nama skrip itu sendiri ke penerjemah yang sebenarnya juga. (Dan kemudian berharap tidak ada yang berbohong ketika mengatur $0.)
ilkkachu
6
Scala sebenarnya memiliki dukungan untuk skrip polyglot dalam sintaksisnya: jika skrip Scala dimulai #!, Scala mengabaikan semuanya hingga pencocokan !#; ini memungkinkan Anda untuk menempatkan kode skrip yang rumit secara sewenang-wenang dalam bahasa yang sewenang-wenang di sana, dan kemudian execmesin eksekusi Scala dengan skrip.
Jörg W Mittag
1
@ Jörg W Mittag: 1 untuk Scala
jrw32982 mendukung Monica
2

Saya lebih suka jawaban Kusalananda dan ilkkachu, tetapi di sini ada alternatif jawaban yang lebih langsung melakukan apa yang ditanyakan, hanya karena ditanyakan.

#!/usr/bin/ruby -e exec "non-existing-interpreter", ARGV[0] rescue exec "python", ARGV[0]

if True:
  print("hello world!")

Perhatikan bahwa Anda hanya dapat melakukan ini ketika penerjemah mengizinkan penulisan kode dalam argumen pertama. Di sini, -edan semuanya setelah itu diambil kata demi kata sebagai 1 argumen untuk ruby. Sejauh yang saya tahu, Anda tidak dapat menggunakan bash untuk kode shebang, karena bash -cmemerlukan kode dalam argumen yang terpisah.

Saya mencoba melakukan hal yang sama dengan python untuk kode shebang:

#!/usr/bin/python -cexec("import sys,os\ntry: os.execlp('non-existing-interpreter', 'non-existing-interpreter', sys.argv[1])\nexcept: os.execlp('ruby', 'ruby', sys.argv[1])")

if true
  puts "hello world!"
end

tapi ternyata terlalu lama dan linux (setidaknya di komputer saya) memotong shebang menjadi 127 karakter. Maafkan penggunaan execuntuk memasukkan baris baru karena python tidak mengizinkan try-exception atau imports tanpa baris baru.

Saya tidak yakin seberapa portabel ini, dan saya tidak akan melakukannya pada kode yang dimaksudkan untuk didistribusikan. Namun demikian, itu bisa dilakukan. Mungkin seseorang akan menemukannya berguna untuk debugging cepat dan kotor atau sesuatu.

JoL
sumber
2

Meskipun ini tidak memilih juru bahasa dalam skrip shell (itu memilihnya per mesin) itu adalah alternatif yang lebih mudah jika Anda memiliki akses administratif ke semua mesin yang Anda coba jalankan skripnya.

Buat symlink (atau hardlink jika diinginkan) untuk menunjuk ke jalur juru bahasa yang diinginkan. Sebagai contoh, pada sistem saya perl dan python berada di / usr / bin:

cd /bin
ln -s /usr/bin/perl perl
ln -s /usr/bin/python python

akan membuat symlink untuk memungkinkan hashbang untuk menyelesaikan / bin / perl, dll. Ini menjaga kemampuan untuk meneruskan parameter ke skrip juga.

rampok
sumber
1
+1 Ini sangat sederhana. Seperti yang Anda perhatikan, itu tidak cukup menjawab pertanyaan, tetapi tampaknya melakukan persis apa yang diinginkan OP. Meskipun saya kira menggunakan env mendapatkan akses root pada setiap masalah mesin.
Joe
0

Saya dihadapkan dengan masalah yang sama seperti ini hari ini ( python3menunjuk ke versi python yang terlalu tua pada satu sistem), dan muncul dengan pendekatan yang sedikit berbeda dari yang dibahas di sini: Gunakan versi "salah" dari python untuk bootstrap ke yang "benar". Batasannya adalah bahwa beberapa versi python harus dapat dijangkau dengan andal, tetapi itu biasanya dapat dicapai dengan misalnya #!/usr/bin/env python3.

Jadi yang saya lakukan adalah memulai skrip saya dengan:

#!/usr/bin/env python3
import sys
import os

# On one of our systems, python3 is pointing to python3.3
# which is too old for our purposes. 'Upgrade' if needed
if sys.version_info[1] < 4:
    for py_version in ['python3.7', 'python3.6', 'python3.5', 'python3.4']:
        try:
            os.execlp(py_version, py_version, *sys.argv)
        except:
            pass # Deliberately ignore errors, pick first available version

Apa yang dilakukan adalah:

  • Periksa versi penerjemah untuk beberapa kriteria penerimaan
  • Jika tidak dapat diterima, buka daftar versi kandidat, dan jalankan kembali dengan yang pertama dari yang tersedia
microtherion
sumber