Czy istnieje sposób na dynamiczne wybranie interpretera wykonującego skrypt? Mam skrypt, który uruchamiam na dwóch różnych systemach, a tłumacz, którego chcę użyć, znajduje się w różnych lokalizacjach na dwóch systemach. Ostatecznie muszę zmienić linię hashbang za każdym razem, gdy się przełączam. Chciałbym zrobić coś, co jest logicznym odpowiednikiem tego (zdaję sobie sprawę, że ta dokładna konstrukcja jest niemożliwa):
if running on system A:
#!/path/to/python/on/systemA
elif running on system B:
#!/path/on/systemB
#Rest of script goes here
Lub jeszcze lepiej byłoby, gdyby próbował użyć pierwszego tłumacza, a jeśli go nie znajdzie, użyje drugiego:
try:
#!/path/to/python/on/systemA
except:
#!path/on/systemB
#Rest of script goes here
Oczywiście mogę zamiast tego wykonać go jako
/path/to/python/on/systemA myscript.py
lub w
/path/on/systemB myscript.py
zależności od tego, gdzie jestem, ale tak naprawdę mam skrypt, który się uruchamia myscript.py
, więc chciałbym określić ścieżkę do interpretera Pythona programowo, a nie ręcznie.
if
warunku nie jest dla ciebie opcją? jak,if something; then /bin/sh restofscript.sh elif...
Odpowiedzi:
Nie, to nie zadziała. Te dwa znaki
#!
absolutnie muszą być pierwszymi dwoma znakami w pliku (jak byś określił, co interpretowałoby instrukcję if?). Stanowi to „magiczną liczbę”, którąexec()
rodzina funkcji wykrywa, gdy określają, czy plik, który zamierzają wykonać, jest skryptem (który wymaga interpretera) lub plikiem binarnym (który tego nie robi).Format linii shebang jest dość ścisły. Musi mieć bezwzględną ścieżkę do tłumacza i co najwyżej jeden argument.
Co można zrobić, to do wykorzystania
env
:Teraz droga
env
jest zwykle/usr/bin/env
, ale technicznie nie jest to żadną gwarancją.Pozwala to dostosować
PATH
zmienną środowiskową w każdym systemie, tak żeinterpreter
(niezależnie od tegobash
,python
czyperl
lub co masz) została znaleziona.Minusem tego podejścia jest to, że niemożliwe będzie przenośne przekazanie argumentu tłumaczowi.
To znaczy że
i
jest mało prawdopodobne, aby działał na niektórych systemach.
Innym oczywistym podejściem jest użycie autotoolów GNU (lub prostszego systemu szablonów) do znalezienia interpretera i umieszczenia poprawnej ścieżki do pliku w
./configure
kroku, który zostanie uruchomiony po zainstalowaniu skryptu w każdym systemie.Można również skorzystać z uruchomienia skryptu z wyraźnym tłumaczem, ale oczywiście tego staramy się unikać:
źródło
#!
musi to nastąpić na początku, ponieważ to nie powłoka przetwarza tę linię. Zastanawiałem się, czy jest jakiś sposób na umieszczenie logiki w linii hashbanga, która byłaby równoważna z if / else. Miałem również nadzieję, że uniknę bałaganu z moim,PATH
ale wydaje mi się, że to moje jedyne opcje.#!/usr/bin/awk
, możesz podać dokładnie jeden argument, as#!/usr/bin/awk -f
. Jeśli wskazywany jest plik binarnyenv
, argumentem jest plik binarny, którego szukaszenv
, jak w#!/usr/bin/env awk
./usr/bin/env
z jednym argumentemawk -f
.foo.awk
za pomocą wiersza hashbang#!/usr/bin/env awk -f
i wywołujesz go./foo.awk
wtedy, w systemie Linux zobaczyszenv
dwa parametryawk -f
i./foo.awk
. Poszukuje/usr/bin/awk -f
(itp.) Spacji.Zawsze możesz utworzyć skrypt opakowujący, aby znaleźć poprawnego interpretera dla rzeczywistego programu:
Zachować opakowanie w użytkowników,
PATH
jakprogram
i umieścić rzeczywistego programu na bok lub z inną nazwą.Użyłem
#!/bin/bash
w hashbangu z powoduflags
tablicy. Jeśli nie musisz przechowywać zmiennej liczby flag lub podobnych i możesz się bez tego obejść, skrypt powinien działać przenośnie#!/bin/sh
.źródło
exec "$interpreter" "${flags[@]}" "$script" "$@"
również, aby zachować czystość drzewa procesów. Propaguje także kod wyjścia.exec
.#!/bin/sh
byłoby lepiej zamiast#!/bin/bash
? Nawet jeśli/bin/sh
jest dowiązaniem symbolicznym do innej powłoki, powinien on istnieć w większości (jeśli nie we wszystkich) * systemach nix, a ponadto zmusiłby autora skryptu do stworzenia przenośnego skryptu, zamiast popadać w bashizmy.flags
jest niestandardową funkcją, ale jest wystarczająca do przechowywania zmiennej liczby flag, więc postanowiłem ją zachować.script=/what/ever; something && exec this "$script" "$@"; exec that "$script" -x -y "$@"
. Możesz także dodać sprawdzanie błędów pod kątem błędów wykonania.Możesz także napisać poliglota (połączyć dwa języki). Na pewno istnieje / bin / sh.
Ma to wadę brzydkiego kodu i być może niektóre
/bin/sh
s mogą się pomylić. Ale można go użyć, gdyenv
nie istnieje lub istnieje gdzieś indziej niż / usr / bin / env. Można go również użyć, jeśli chcesz dokonać dość fantazyjnego wyboru.Pierwsza część skryptu określa, którego interpretera należy użyć, gdy jest uruchamiany z / bin / sh jako interpreter, ale jest ignorowana, gdy jest uruchamiany przez poprawnego interpretera. Użyj,
exec
aby zapobiec uruchomieniu powłoki przez więcej niż pierwszą część.Przykład w języku Python:
źródło
exec "$interpreter" "$0" "$@"
przekazać nazwę samego skryptu również rzeczywistemu tłumaczowi. (I wtedy mam nadzieję, że nikt nie skłamał podczas konfiguracji$0
.)#!
, Scala ignoruje wszystko, aż do dopasowania!#
; pozwala to umieścić dowolnie złożony kod skryptu w dowolnym języku, a następnieexec
silnik wykonawczy Scala ze skryptem.Wolę odpowiedzi Kusalanandy i ilkkachu, ale tutaj jest alternatywna odpowiedź, która bardziej bezpośrednio robi to, o co pytało pytanie, po prostu dlatego, że zostało zadane.
Zauważ, że możesz to zrobić tylko wtedy, gdy interpreter pozwala na pisanie kodu w pierwszym argumencie. Tutaj,
-e
a wszystko po tym jest traktowane dosłownie jako 1 argument za ruby. O ile mi wiadomo, nie można użyć bash dla kodu shebang, ponieważbash -c
wymaga, aby kod był osobnym argumentem.Próbowałem zrobić to samo z pythonem dla kodu shebang:
ale okazuje się, że jest za długi i linux (przynajmniej na mojej maszynie) obcina shebang do 127 znaków. Proszę wybaczyć użycie
exec
wstawiania nowych linii, ponieważ Python nie zezwala na try-wyjątki lubimport
s bez nowych linii.Nie jestem pewien, jak to jest przenośne i nie zrobiłbym tego na kodzie przeznaczonym do dystrybucji. Niemniej jednak jest to wykonalne. Może ktoś uzna to za przydatne do szybkiego i brudnego debugowania lub czegoś takiego.
źródło
Chociaż nie wybiera to interpretera w skrypcie powłoki (wybiera go na maszynę), jest łatwiejszą alternatywą, jeśli masz dostęp administracyjny do wszystkich komputerów, na których próbujesz uruchomić skrypt.
Utwórz dowiązanie symboliczne (lub w razie potrzeby dowiązanie twarde), aby wskazać żądaną ścieżkę tłumacza. Na przykład w moim systemie perl i python znajdują się w / usr / bin:
utworzyłby dowiązanie symboliczne, aby umożliwić hashbangowi rozpoznanie dla / bin / perl itp. Zachowuje to również możliwość przekazywania parametrów do skryptów.
źródło
Zetknąłem się dzisiaj z podobnym problemem takim jak ten (
python3
wskazując na wersję Pythona, która była zbyt stara w jednym systemie), i wpadłem na podejście nieco inne niż omówione tutaj: użyj „złej” wersji python, aby załadować się do „właściwego”. Ograniczeniem jest to, że niektóre wersje Pythona muszą być osiągalne w sposób niezawodny, ale zwykle można to osiągnąć np#!/usr/bin/env python3
.Więc zaczynam mój skrypt od:
To robi:
źródło