Betik başladıktan sonra tercüman seçin, örneğin hashbang içinde / else içinde


16

Bir komut dosyasını çalıştıran yorumlayıcıyı dinamik olarak seçmenin bir yolu var mı? İki farklı sistemde çalıştırdığım bir senaryom var ve kullanmak istediğim tercüman iki sistemde farklı yerlerde bulunuyor. Ne kadar bitirmek zorunda kaldım her zaman hashbang hattı değiştirmek. Ben bunun mantıksal eşdeğer bir şey yapmak istiyorum (Bu kesin yapı imkansız olduğunu fark):

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

#Rest of script goes here

Ya da daha iyisi bu olurdu, böylece ilk yorumlayıcıyı kullanmaya çalışır ve bulamazsa ikincisini kullanır:

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

#Rest of script goes here

Açıkçası, bunun yerine nerede olduğuma bağlı olarak /path/to/python/on/systemA myscript.py veya çalıştırabilirim /path/on/systemB myscript.py, ancak aslında başlatılan bir sarıcı komut dosyası var myscript.py, bu yüzden python yorumlayıcısının yolunu programlı olarak elle değil belirtmek istiyorum.


3
'script kalanını' dosyalama olmadan yorumlayıcıya dosya olarak aktarmak ve ifkoşulu kullanmak sizin için bir seçenek değil mi? gibi,if something; then /bin/sh restofscript.sh elif...
mazs

Bu bir seçenek, ben de düşündüm, ama istediğimden biraz daha karışık. Hashbang hattındaki mantık imkansız olduğundan, sanırım gerçekten bu rotaya gideceğim.
dkv

Bu sorunun oluşturduğu çok çeşitli cevapları seviyorum.
Oskar Skog

Yanıtlar:


27

Hayır, bu işe yaramaz. İki karakterin #!kesinlikle dosyadaki ilk iki karakter olması gerekir (if-ifadesini neyin yorumladığını nasıl belirlersiniz?). Bu exec(), işlev ailesinin yürütmek üzere oldukları bir dosyanın bir komut dosyası (yorumlayıcı gerektiren) veya ikili dosya (olmayan) olup olmadığını belirlediklerinde algıladığı "sihirli sayı" yı oluşturur.

Mesele hattının formatı oldukça katıdır. Bir tercümana giden mutlak bir yola ve en fazla bir argümana sahip olması gerekir.

Ne yapabilirsiniz yapmak kullanıma etmektir env:

#!/usr/bin/env interpreter

Şimdi, yolu envolan , genellikle /usr/bin/env hiçbir garantisi olduğunu teknik olarak ama.

Bu PATH, her sistemdeki ortam değişkenini interpreter(ister o bash, pythonister perlsahip olun) bulunacak şekilde ayarlamanızı sağlar.

Bu yaklaşımın bir dezavantajı, tercümana portatif olarak bir argüman iletmenin imkansız olmasıdır.

Bunun anlamı şudur ki

#!/usr/bin/env awk -f

ve

#!/usr/bin/env sed -f

bazı sistemlerde çalışma olasılığı düşüktür.

Başka bir belirgin yaklaşım, yorumlayıcıyı bulmak ve ./configureher bir sisteme komut dosyası yüklendikten sonra çalıştırılacak bir adımda dosyaya doğru yolu yerleştirmek için GNU otomatik araçlarını (veya daha basit bir şablonlama sistemini) kullanmaktır .

Biri de betiği açık bir tercümanla çalıştırmaya başvurabilir, ancak bundan kaçınmaya çalıştığınız açıktır:

$ sed -f script.sed

Doğru, #!bunun başlangıçta olması gerektiğini anlıyorum , çünkü o hattı işleyen kabuk değil. Mantığı koymak için bir yol var acaba içeride if / else eşdeğer olacağını hashbang hattı. Ben de ile uğraşmaktan kaçınmayı umuyordum PATHama sanırım bunlar benim tek seçeneklerim.
dkv

1
Kullandığınızda #!/usr/bin/awk, tam olarak bir argüman sunabilirsiniz #!/usr/bin/awk -f. İkili sen işaret olduğu için ise env, bağımsız değişken soruyorsun ikili olduğu envgibi aramaya #!/usr/bin/env awk.
DopeGhoti

2
@dkv Değil. İki argümanı olan bir tercüman kullanır ve bazı sistemlerde çalışabilir, ancak kesinlikle hepsi üzerinde değil.
Kusalananda

3
@dkv Linux üzerinde /usr/bin/envtek argümanla çalışır awk -f.
ilkkachu

1
@Kusalananda, hayır, mesele buydu. foo.awkHashbang hattı ile adlandırılan bir komut dosyanız varsa #!/usr/bin/env awk -fve o zaman ile çağırırsanız ./foo.awk, Linux'ta, enviki parametre awk -fve ./foo.awk. Aslında /usr/bin/awk -fbir boşluk (vb.) Aramaya gider .
ilkkachu

27

Gerçek program için doğru yorumlayıcıyı bulmak için her zaman bir sarıcı komut dosyası oluşturabilirsiniz:

#!/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" "$@"

Sarıcıyı kullanıcıların PATHolarak kaydedin ve programgerçek programı bir kenara veya başka bir adla koyun.

Ben dizi #!/bin/bashnedeniyle hashbang kullanılır flags. Değişken sayıda bayrak veya benzeri bir şey depolamanız gerekmiyorsa ve onsuz da yapabilirseniz, komut dosyası taşınabilir bir şekilde çalışmalıdır #!/bin/sh.


2
Gördüğüm exec "$interpreter" "${flags[@]}" "$script" "$@"de süreç ağaç temizleyici tutmak için kullanılır. Ayrıca çıkış kodunu da yayar.
rrauenza

@rrauenza, ah evet, doğal olarak exec.
ilkkachu

1
#!/bin/shBunun yerine daha iyi olmaz mıydı #!/bin/bash? /bin/shFarklı bir kabuk için bir sembolik bağlantı olsa bile , çoğu (hepsi değilse de) * nix sistemlerinde var olmalıdır, ayrıca komut dosyası yazarını bashisms yerine taşınabilir bir komut dosyası yapmaya zorlar.
Sergiy Kolodyazhnyy

@SergiyKolodyazhnyy, heh, daha önce bahsetmeyi düşündüm, ama o zaman olmadı. Kullanılan dizi flagsstandart olmayan bir özelliktir, ancak değişken sayıda bayrak saklamak için yeterince yararlıdır, bu yüzden tutmaya karar verdim.
ilkkachu

Veya kullanım / bin / sh ve sadece her dalında doğrudan tercüman çağırır: script=/what/ever; something && exec this "$script" "$@"; exec that "$script" -x -y "$@". Ayrıca yürütme hataları için hata denetimi de ekleyebilirsiniz.
jrw32982 Monica

11

Ayrıca bir çok dilek yazabilirsiniz (iki dili birleştirin). / bin / sh olması garantilidir.

Bu, çirkin kodun dezavantajına sahiptir ve belki de bazılarının /bin/shkafası karışabilir. Ancak env/ usr / bin / env dışında bir yerde mevcut olmadığında veya mevcut olmadığında kullanılabilir . Bazı oldukça süslü bir seçim yapmak istiyorsanız da kullanılabilir.

Komut dosyasının ilk bölümü, / bin / sh ile tercüman olarak çalıştırıldığında hangi tercümanın kullanılacağını belirler, ancak doğru tercüman tarafından çalıştırıldığında yok sayılır. execKabuğun ilk parçadan daha fazla çalışmasını önlemek için kullanın .

Python örneği:

#!/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" "$@"
'''

3
Sanırım daha önce bunlardan birini gördüm, ama fikir hala aynı derecede korkunç ... Ama muhtemelen exec "$interpreter" "$0" "$@"senaryonun adını gerçek tercümana da almak istiyorsun . (Ve sonra kurulum sırasında kimse yalan $0
söylemez

6
Scala aslında sözdiziminde çok dilli komut dosyalarını destekler: Bir Scala betiği ile başlarsa #!, Scala bir eşleşmeye kadar her şeyi yok sayar !#; bu, rasgele karmaşık bir kod kodunu rastgele bir dilde ve daha sonra execkomut dosyasıyla birlikte Scala yürütme motorunu koymanıza izin verir .
Jörg W Mittag

1
@ Jörg W Mittag: Scala için +1
jrw32982 Monica'yı

2

Ben Kusalananda'nın ve ilkkachu'nun cevaplarını tercih ediyorum, ama burada sorulan şeyin doğrudan sorulduğu alternatif bir cevap var.

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

if True:
  print("hello world!")

Bunu yalnızca yorumlayıcı ilk bağımsız değişkene kod yazmaya izin verdiğinde yapabileceğinizi unutmayın. Burada -eve ondan sonraki her şey yakut için 1 argüman olarak kelimesi kelimesine alınır. Anlayabildiğim kadarıyla, shebang kodu için bash kullanamazsınız, çünkü bash -ckodun ayrı bir argümanda olmasını gerektirir.

Ben shebang kodu için python ile aynı yapmaya çalıştı:

#!/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

ama çok uzun çıkıyor ve linux (en azından makinemde) 127 karaktere kadar kesimi kesiyor. Lütfen exec, python try-exception'a veya newline importiçermeyen yeni satırlara izin vermek için yeni satır eklemek için kullanmayın .

Bunun ne kadar taşınabilir olduğundan emin değilim ve dağıtılması gereken kodda yapmazdım. Yine de, yapılabilir. Belki birisi hızlı ve kirli hata ayıklama veya başka bir şey için yararlı bulacaktır.


2

Bu, kabuk betiği içindeki yorumlayıcıyı seçmese de (makine başına seçer), betiği çalıştırmaya çalıştığınız tüm makinelere yönetici erişiminiz varsa daha kolay bir alternatiftir.

İstediğiniz yorumlayıcı yolunu işaret etmek için bir sembolik bağlantı (veya isterseniz bir bağlantı) oluşturun. Örneğin, sistemimde perl ve python / usr / bin dizininde:

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

hashbang komutunun / bin / perl, vb. için çözümlemesine izin veren bir sembolik bağlantı oluşturacaktır. Bu, parametreleri komut dosyalarına da aktarma özelliğini korur.


1
+1 Bu çok basit. Belirttiğiniz gibi, soruya tam olarak cevap vermiyor, ancak OP'nin tam olarak ne istediğini yapıyor gibi görünüyor. Env kullanarak her makine sorunu kök erişim etrafında alır sanırım.
Joe

0

Bugün böyle bir sorunla karşı karşıya kaldım ( python3bir sistemde çok eski bir python sürümüne işaret ederek) ve burada tartışılanlardan biraz farklı bir yaklaşım ortaya çıktı: python "doğru" bir içine bootstrap. Sınırlama, python'un bazı sürümlerinin güvenilir bir şekilde erişilebilir olması gerektiğidir, ancak bu genellikle örn #!/usr/bin/env python3.

Yani yaptığım şey senaryomla başlamak:

#!/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

Bunun yaptığı:

  • Bazı kabul kriterleri için tercüman versiyonunu kontrol edin
  • Kabul edilemezse, aday sürümlerin bir listesini gözden geçirin ve mevcut olanlardan ilkiyle yeniden yürütün
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.