Zastanawiam się, czy istnieje ogólny sposób przekazywania wielu opcji do pliku wykonywalnego za pomocą linii shebang ( #!
).
Używam NixOS, a pierwsza część shebang w każdym skrypcie, który piszę, jest zwykle /usr/bin/env
. Problem, z którym się wtedy spotykam, polega na tym, że wszystko, co następuje, jest interpretowane przez system jako pojedynczy plik lub katalog.
Załóżmy na przykład, że chcę napisać skrypt do wykonania bash
w trybie posix. Naiwnym sposobem pisania shebang byłoby:
#!/usr/bin/env bash --posix
ale próba wykonania wynikowego skryptu powoduje następujący błąd:
/usr/bin/env: ‘bash --posix’: No such file or directory
Znam ten post , ale zastanawiałem się, czy istnieje bardziej ogólne i czystsze rozwiązanie.
EDYCJA : Wiem, że w przypadku skryptów Guile istnieje sposób na osiągnięcie tego, czego chcę, udokumentowany w Sekcji 4.3.4 podręcznika:
#!/usr/bin/env sh
exec guile -l fact -e '(@ (fac) main)' -s "$0" "$@"
!#
Sztuczka polega na tym, że druga linia (od exec
) jest interpretowana jako kod, sh
ale będąc w #!
... !#
bloku, jako komentarz, a zatem ignorowana przez interpretera Guile'a.
Czy nie byłoby możliwe uogólnienie tej metody na żadnego tłumacza?
Druga EDYCJA : Po krótkiej zabawie wydaje się, że w przypadku tłumaczy, którzy potrafią odczytać dane wejściowe stdin
, zadziała następująca metoda:
#!/usr/bin/env sh
sed '1,2d' "$0" | bash --verbose --posix /dev/stdin; exit;
Prawdopodobnie nie jest to optymalne, ponieważ sh
proces trwa, dopóki tłumacz nie zakończy pracy. Wszelkie uwagi lub sugestie będą mile widziane.
Odpowiedzi:
Nie ma ogólnego rozwiązania, przynajmniej jeśli potrzebujesz obsługi Linuksa, ponieważ jądro Linuksa traktuje wszystko następujące po pierwszym „słowie” w wierszu shebang jako pojedynczy argument .
Nie jestem pewien, jakie są ograniczenia NixOS, ale zazwyczaj po prostu napisałbym twój shebang jako
lub, gdzie to możliwe, ustaw opcje w skrypcie :
Ewentualnie możesz uruchomić skrypt ponownie, używając odpowiedniego wywołania powłoki:
Podejście to można uogólnić na inne języki, o ile znajdziesz sposób, aby pierwsze kilka wierszy (które są interpretowane przez powłokę) były ignorowane przez język docelowy.
GNU
coreutils
"env
stanowi obejście od wersji 8.30, patrz unode „s odpowiedź szczegóły. (Jest to dostępne w Debianie 10 i nowszych wersjach, RHEL 8 i nowszych wersjach, Ubuntu 19.04 i nowszych wersjach itp.)źródło
Chociaż nie do końca przenośny, począwszy od Coreutils 8.30 i zgodnie z jego dokumentacją będziesz mógł używać:
Biorąc pod uwagę:
dostaniesz:
a jeśli jesteś ciekawy
showargs
to:źródło
env
gdzie-S
została dodana w 2005 roku. Zobacz lists.gnu.org/r/coreutils/2018-04/msg00011.htmlshowargs
: pastebin.com/q9m6xr8H i pastebin.com/gS8AQ5WA (one-liner)env
zawiera własnąshowargs
: opcję -v np.#!/usr/bin/env -vS --option1 --option2 ...
Standard POSIX bardzo zwięźle opisuje
#!
:Z sekcji uzasadnienia dokumentacji
exec()
rodziny interfejsów systemowych :Z sekcji Wprowadzenie do powłoki :
Zasadniczo oznacza to, że każda implementacja (Unix, którego używasz) może dowolnie wykonywać specyfikację parsowania linii shebang.
Niektóre Unices, takie jak macOS (nie można przetestować bankomatu), podzielą argumenty podane interpreterowi w linii shebang na osobne argumenty, podczas gdy Linux i większość innych Unices poda te argumenty jako jedną opcję dla interpretera.
Dlatego nierozsądne jest poleganie na tym, że linia shebang może przyjąć więcej niż jeden argument.
Zobacz także sekcję Przenośność w artykule Shebang na Wikipedii .
Jednym łatwym rozwiązaniem, które można uogólnić na dowolny program narzędziowy lub język, jest utworzenie skryptu opakowującego, który wykonuje prawdziwy skrypt z odpowiednimi argumentami wiersza poleceń:
Nie sądzę, bym osobiście spróbował zmusić go do ponownego uruchomienia , ponieważ wydaje się to nieco kruche.
źródło
Shebang jest opisany na stronie podręcznika
execve
(2) w następujący sposób:W tej składni akceptowane są dwa spacje:
Zauważ, że nie użyłem liczby mnogiej, gdy mówię o opcjonalnym argumencie
[optional-arg ...]
, podobnie jak powyższa składnia , ponieważ możesz podać najwyżej jeden argument .Jeśli chodzi o skrypty powłoki, możesz użyć
set
wbudowanej komendy na początku skryptu, która pozwoli ustawić parametry interpretera, zapewniając taki sam wynik, jak w przypadku użycia argumentów wiersza polecenia.W Twoim przypadku:
W wierszu polecenia Bash sprawdź dane wyjściowe opcji,
help set
aby uzyskać wszystkie dostępne opcje.źródło
W Linuksie shebang nie jest zbyt elastyczny; według wielu odpowiedzi ( odpowiedź Stephena KITT i Jörg W Mittag użytkownika ), nie ma wyznaczonego sposób przekazać wiele argumentów w shebang linii.
Nie jestem pewien, czy przyda się komukolwiek, ale napisałem krótki skrypt, aby zaimplementować brakującą funkcję. Zobacz https://gist.github.com/loxaxs/7cbe84aed1c38cf18f70d8427bed1efa .
Możliwe jest również pisanie wbudowanych obejść. Poniżej przedstawiam cztery obejścia niezależne od języka zastosowane w tym samym skrypcie testowym, a wynik każdego z nich drukowany. Przypuszczam, że skrypt jest wykonywalny i jest w
/tmp/shebang
.Zawijanie skryptu w bash heredoc wewnątrz substytucji procesu
O ile mi wiadomo, jest to najbardziej niezawodny, niezależny od języka sposób robienia tego. Pozwala przekazywać argumenty i zachowuje standardowe wejście. Wadą jest to, że tłumacz nie zna (rzeczywistej) lokalizacji pliku, który czyta.
Wywoływanie
echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
wydruków:Zauważ, że podstawienie procesu tworzy specjalny plik. To może nie pasować do wszystkich plików wykonywalnych. Na przykład
#!/usr/bin/less
narzeka:/dev/fd/63 is not a regular file (use -f to see it)
Nie wiem, czy jest możliwe posiadanie heredoc wewnątrz podstawienia procesu w desce rozdzielczej.
Zawijanie skryptu w prosty heredoc
Krótsze i prostsze, ale nie będziesz mieć dostępu do
stdin
skryptu, a interpreter musi móc odczytać i wykonać skryptstdin
.Wywoływanie
echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
wydruków:Użyj
system()
wywołania awk, ale bez argumentówPrawidłowo przekazuje nazwę wykonanego pliku, ale skrypt nie odbiera argumentów, które mu podajesz. Zauważ, że awk jest jedynym znanym mi językiem, którego interpreter jest domyślnie zainstalowany na Linuksie i domyślnie odczytuje instrukcje z wiersza poleceń.
Wywoływanie
echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
wydruków:Użyj
system()
wywołania awk 4.1+ , pod warunkiem, że twoje argumenty nie zawierają spacjiFajnie, ale tylko wtedy, gdy masz pewność, że twój skrypt nie zostanie wywołany z argumentami zawierającymi spacje. Jak widać, twoje argumenty zawierające spacje zostałyby podzielone, chyba że spacje zostaną usunięte.
Wywoływanie
echo -e 'aa\nbb' | /tmp/shebang 'arg1' 'arg2 contains spaces' 'arg3\ uses\ \\escapes\\'
wydruków:W przypadku wersji awk poniżej 4.1 będziesz musiał użyć konkatenacji łańcuchów wewnątrz pętli for, patrz przykładowa funkcja https://www.gnu.org/software/gawk/manual/html_node/Join-Function.html .
źródło
$variable
lub`command`
zastąpić:exec python3 -O <(cat <<'EOWRAPPER'
Trik do użycia
LD_LIBRARY_PATH
z#!
pythonem w wierszu (shebang), który nie zależy od niczego innego niż powłoka i działa przysmak:Jak wyjaśniono gdzie indziej na tej stronie, niektóre powłoki
sh
mogą pobierać skrypt na standardowe wejście.Skrypt, który dajemy,
sh
próbuje wykonać polecenie,''''
które jest uproszczone''
(pusty ciąg znaków)sh
i oczywiście nie wykonuje go, ponieważ nie ma''
polecenia, więc zwykle wypisujeline 2: command not found
na standardowym deskryptorze błędów, ale przekierowujemy ten komunikat za2>/dev/null
pomocą najbliższa czarna dziura, ponieważ wyświetlenie jej byłoby nieporządne i mylące dla użytkownikash
.Następnie przechodzimy do interesującego nas polecenia:
exec
które zastępuje bieżący proces powłoki następującym, w naszym przypadku:/usr/bin/env python
odpowiednimi parametrami:"$0"
aby python wiedział, który skrypt powinien otworzyć i zinterpretować, a także ustawićsys.argv[0]
"$@"
aby ustawić pythonsys.argv[1:]
na argumenty przekazywane w wierszu poleceń skryptu.Prosimy również o
env
ustawienieLD_LIBRARY_PATH
zmiennej środowiskowej, która jest jedynym punktem włamania.Polecenie powłoki kończy się na komentarzu rozpoczynającym się od,
#
dzięki czemu powłoka ignoruje końcowe potrójne cudzysłowy'''
.sh
jest następnie zastępowane przez shinny nową instancję interpretera Pythona, która otwiera i odczytuje skrypt źródłowy Pythona podany jako pierwszy argument (the"$0"
).Python otwiera plik i pomija pierwszy wiersz źródła dzięki
-x
argumentowi. Uwaga: działa również bez,-x
ponieważ dla Pythona shebang jest tylko komentarzem .Następnie Python interpretuje drugi wiersz jako ciąg docstring dla bieżącego pliku modułu, więc jeśli potrzebujesz poprawnego docstring modułu, po prostu ustaw
__doc__
pierwszą rzecz w swoim programie python, tak jak w powyższym przykładzie.źródło
''''exec ...
powinieneś wykonać zadanie. Zauważ, że nie ma spacji przed exec, bo spowoduje, że będzie szukał pustego polecenia. Chcesz podzielić puste na pierwszy argument, więc tak$0
jestexec
.Znalazłem dość głupie obejście, gdy szukałem pliku wykonywalnego, który wyłącza skrypt jako pojedynczy argument:
źródło