Jak używać wielu argumentów dla awk z shebangiem (tj. #!)?

118

Chciałbym wykonać skrypt gawk z --re-intervalużyciem shebang. „Naiwne” podejście

#!/usr/bin/gawk --re-interval -f
... awk script goes here

nie działa, ponieważ gawk jest wywoływany z pierwszym argumentem "--re-interval -f"(niepodzielonym na białe znaki), którego nie rozumie. Czy istnieje obejście tego problemu?

Oczywiście możesz albo nie wywołać gawk bezpośrednio, ale zawinąć go w skrypt powłoki, który rozdziela pierwszy argument, lub stworzyć skrypt powłoki, który następnie wywoła gawk i umieści skrypt w innym pliku, ale zastanawiałem się, czy jest jakiś sposób do zrobienia to w jednym pliku.

Zachowanie linii shebang różni się w zależności od systemu - przynajmniej w Cygwin nie dzieli argumentów białymi spacjami. Po prostu obchodzi mnie, jak to zrobić w systemie, który zachowuje się w ten sposób; skrypt nie jest przeznaczony do przenoszenia.

Hans-Peter Störr
źródło
1
Głupi eksperyment, który właśnie przeprowadziłem, polegał na tym, że jeden skrypt użył innego skryptu w linii shebang, co poprawnie podzieliło argumenty.
Hasturkun
@Hasturkun, to rodzi inny problem, że zachowanie linii shebang również różni się w zależności od systemu, biorąc pod uwagę, czy wywoływany program może sam być skryptem.
dubiousjim
W ostatnich wersjach gawk (> = 4.0) --re-intervalnie jest już potrzebny (patrz [ gnu.org/software/gawk/manual/… ).

Odpowiedzi:

25

Wydaje się, że działa to dla mnie z (g) awk.

#!/bin/sh
arbitrary_long_name==0 "exec" "/usr/bin/gawk" "--re-interval" "-f" "$0" "$@"


# The real awk program starts here
{ print $0 }

Zwróć uwagę na #!przebiegi /bin/sh, więc ten skrypt jest najpierw interpretowany jako skrypt powłoki.

Na początku po prostu próbowałem "exec" "/usr/bin/gawk" "--re-interval" "-f" "$0" "$@", ale awk traktował to jako polecenie i bezwarunkowo drukował każdy wiersz wejścia. Dlatego stawiam arbitrary_long_name==0- ma cały czas zawodzić. Mógłbyś go zastąpić jakimś bełkotem. Zasadniczo szukałem fałszywego warunku w awk, który nie wpłynąłby negatywnie na skrypt powłoki.

W skrypcie powłoki arbitrary_long_name==0definiuje zmienną o nazwie arbitrary_long_namei ustawia ją na =0.

Aaron McDaid
źródło
To moja odpowiedź, ale zastanawiam się, czy jest wystarczająco przenośny i solidny. Czy zależy to konkretnie od bash, czy będzie działać z jakimkolwiek POSIX sh? I nie używam awkczęsto, więc nie jestem pewien, czy moja sztuczka na drugiej linii jest dobrym sposobem na wymuszenie awkignorowania linii.
Aaron McDaid
Właśnie nad tym się zastanawiałem, +1, ale prawdopodobnie niewskazane (stąd względne głosy).
Aaron Hall
Czy możesz wyjaśnić, jakie może to mieć problemy, @AaronHall? Dopóki zmienna arbitrary_long_namenie koliduje ze zmienną używaną w prawdziwym programie awk, nie widzę żadnego problemu. Czy jest coś, czego mi brakuje?
Aaron McDaid,
Użyj #!/bin/sh -zamiast, #!/bin/shaby chronić skrypt przed możliwym niewłaściwym zachowaniem się w niebezpieczny sposób, jeśli zostanie wywołany z argumentem zerowym, który ma -jako pierwszy znak. Może się to zdarzyć przypadkowo w językach programowania, takich jak C, gdzie łatwo jest przypadkowo zepsuć, zapominając o przekazaniu wywoływanej nazwy programu jako części tablicy argumentów do execvei podobnych funkcji, a jeśli ludzie zwykle zapominają się przed tym chronić, może również w końcu jest ostatnim krokiem w złośliwie możliwej do wykorzystania luce, która umożliwia atakującemu uzyskanie interaktywnej powłoki.
mtraceur
161

Linia shebang nigdy nie została określona jako część specyfikacji POSIX, SUS, LSB ani żadnej innej specyfikacji. AFAIK, nie zostało to nawet odpowiednio udokumentowane.

Istnieje przybliżony konsensus co do tego, co robi: weź wszystko między !a a \ni execit. Założenie jest takie, że wszystko pomiędzy !i a \njest pełną, absolutną ścieżką do tłumacza. Nie ma zgody co do tego, co się stanie, jeśli zawiera spacje.

  1. Niektóre systemy operacyjne po prostu traktują całość jako ścieżkę. W końcu w większości systemów operacyjnych białe znaki lub myślniki są dozwolone na ścieżce.
  2. Niektóre systemy operacyjne dzielą się na białe znaki i traktują pierwszą część jako ścieżkę do interpretera, a resztę jako indywidualne argumenty.
  3. Niektóre systemy operacyjne dzielą się na pierwszej białej spacji i traktują przednią część jako ścieżkę do interpetera, a resztę jako pojedynczy argument (czyli to, co widzisz).
  4. Niektórzy nawet nie obsługują shebang linie w ogóle .

Na szczęście 1. i 4. wydają się wymarły, ale 3. jest dość rozpowszechnione, więc po prostu nie można polegać na możliwości przekazania więcej niż jednego argumentu.

A ponieważ lokalizacja poleceń nie jest również wymieniony w POSIX lub SUS, zazwyczaj zużywają że jednym argumentem przepuszczając wykonywalnego nazwę na envtak, to możemy określić lokalizacji wykonywalnego; na przykład:

#!/usr/bin/env gawk

[Oczywiście, nadal zakłada to określoną ścieżkę env, ale jest tylko kilka systemów, w których żyje /bin, więc jest to ogólnie bezpieczne. Lokalizacja envjest o wiele bardziej ustandaryzowana niż lokalizacja gawklub nawet gorzej, jak pythonlub rubylub spidermonkey.]

Co oznacza, że nie można rzeczywiście używają żadnych argumentów w ogóle .

Jörg W Mittag
źródło
1
Środowisko FreeBSD ma -Sprzełącznik, który pomaga tutaj, ale nie ma go w moim Linuksie envi podejrzewam, że nie jest również dostępny na gygwin. @hstoerr, inni użytkownicy w innych sytuacjach mogą później czytać twoje pytania, więc generalnie preferowane są przenośne odpowiedzi, nawet jeśli teraz nie potrzebujesz przenośności.
dubiousjim
4
Więc nie możemy przenośnie używać argumentów w shebangu. Ale co, jeśli potrzebujemy argumentów za wszelką cenę? Zgaduję, że rozwiązaniem jest napisanie skryptu powłoki zawierającego #!/bin/shi /usr/bin/env gawk --re-interval -f my-script.awk. Czy to jest poprawne?
Rory O'Kane,
1
Nie zgadzam się. Możesz całkiem przenośnie użyć jednego argumentu. Każdy system, w którym nie można użyć żadnych argumentów, zawodzi żałośnie w implementacji tego tradycyjnego uniksizmu, którym jest hash-bang. Jeśli nie-implementacje są uczciwą grą, możemy śmiało powiedzieć, że #!samo w sobie nie jest przenośne. Na przykład system Windows w ogóle nie rozpoznaje tej konwencji „natywnie”. W Uniksie tradycyjnie potrzebny jest jeden argument, aby móc to zrobić #!/usr/bin/awk -f.
Kaz,
7
@Kaz: Tak, ale ponieważ ścieżki wielu plików binarnych nie są ustandaryzowane, zużywasz jeden argument za #!/usr/bin/env rubylub lajki.
Jörg W Mittag
3
@Pacerier: zmień specyfikację POSIX i poczekaj 20-30 lat, aż wszystkie systemy zostaną zaktualizowane, aby były zgodne ze specyfikacją.
Jörg W Mittag,
18

Chociaż nie do końca przenośny, zaczynając od coreutils 8.30 i zgodnie z jego dokumentacją będziesz mógł używać:

#!/usr/bin/env -S command arg1 arg2 ...

Więc biorąc pod uwagę:

$ cat test.sh
#!/usr/bin/env -S showargs here 'is another' long arg -e "this and that " too

dostaniesz:

% ./test.sh 
$0 is '/usr/local/bin/showargs'
$1 is 'here'
$2 is 'is another'
$3 is 'long'
$4 is 'arg'
$5 is '-e'
$6 is 'this and that '
$7 is 'too'
$8 is './test.sh'

a jeśli jesteś ciekawy showargsto:

#!/usr/bin/env sh
echo "\$0 is '$0'"

i=1
for arg in "$@"; do
    echo "\$$i is '$arg'"
    i=$((i+1))
done

Oryginalna odpowiedź tutaj .

unode
źródło
1
FYI, FreeBSD ma -S od lat (od 6.0). To mile widziany dodatek do coreutils, który ułatwia przenoszenie.
Juan
12

Natknąłem się na ten sam problem, bez widocznego rozwiązania ze względu na sposób, w jaki obsługiwane są białe znaki w shebang (przynajmniej w Linuksie).

Można jednak przekazać kilka opcji w shebang, tak długo jak są one krótkie opcje i mogą być łączone (drogę GNU).

Na przykład nie możesz tego mieć

#!/usr/bin/foo -i -f

ale możesz mieć

#!/usr/bin/foo -if

Oczywiście działa to tylko wtedy, gdy opcje mają krótkie odpowiedniki i nie pobierają żadnych argumentów.

ℝaphink
źródło
11

Pod Cygwinem i Linuksem wszystko po ścieżce shebang jest analizowane do programu jako jeden argument.

Można to obejść, używając innego awkskryptu wewnątrz shebang:

#!/usr/bin/gawk {system("/usr/bin/gawk --re-interval -f " FILENAME); exit}

Spowoduje to wykonanie {system("/usr/bin/gawk --re-interval -f " FILENAME); exit}w awk.
I to zostanie wykonane /usr/bin/gawk --re-interval -f path/to/your/script.awkw powłoce twojego systemu.

Moritz
źródło
2
to nie zadziała, jeśli przekażesz argumenty do scenariusza
Steven Penny
4
#!/bin/sh
''':'
exec YourProg -some_options "$0" "$@"
'''

Powyższa sztuczka z shebangiem powłoki jest bardziej przenośna niż /usr/bin/env.

user3123730
źródło
Znak „” „:” jest zastawem, ponieważ moje oryginalne rozwiązanie dotyczyło skryptu w języku Python, więc „”: ”mówi interpreterowi języka Python, aby zignorował część exec.
user3123730
4
Myślę, że jesteś odrzucany, ponieważ twoje rozwiązanie jest dla python, ale to pytanie dotyczy awk.
Aaron McDaid
1
Świetny hack dla Pythona.
Zaar Hai
3

W podręczniku do gawk (http://www.gnu.org/manual/gawk/gawk.html) na końcu sekcji 1.14 zauważ, że podczas uruchamiania gawk z linii shebang należy używać tylko jednego argumentu. Mówi, że system operacyjny potraktuje wszystko po ścieżce do gawk jako pojedynczy argument. Być może istnieje inny sposób określenia --re-intervalopcji? Być może twój skrypt może odwoływać się do twojej powłoki w linii shebang, działać gawkjako polecenie i dołączyć tekst twojego skryptu jako „dokument tutaj”.

bta
źródło
Wygląda na to, że nie ma innego sposobu określenia opcji. Masz rację: gawk -f - << EOF, niektóre linie skryptów, EOF działa, ale uniemożliwia mi to czytanie standardowego wejścia za pomocą gawk.
Hans-Peter Störr
Ten dokument zjada standardowy strumień wejściowy dla gawk, ale nadal możesz być w stanie przesłać coś potokiem przez stderr (to znaczy przekierować stdout na stderr przed potokowaniem do tego skryptu). Nigdy tego nie próbowałem, ale jeśli pierwszy proces nie emituje niczego na stderr, może to zadziałać. Możesz także utworzyć nazwany potok ( linuxjournal.com/content/using-named-pipes-fifos-bash ), jeśli chcesz się upewnić, że nic innego go nie używa.
bta
3

Dlaczego nie użyć bashi gawksiebie, aby pominąć shebang, przeczytać skrypt i przekazać go jako plik do drugiej instancji gawk [--with-whatever-number-of-params-you-need]?

#!/bin/bash
gawk --re-interval -f <(gawk 'NR>3' $0 )
exit
{
  print "Program body goes here"
  print $1
}

(-the samo może oczywiście być również realizowana np sedalbo tail, ale myślę, że istnieje jakiś rodzaj piękna w zależności tylko od bashi gawksobie;)

Conny
źródło
0

Dla zabawy: istnieje następujące dość dziwne rozwiązanie, które przekierowuje stdin i program przez deskryptory plików 3 i 4. Możesz także utworzyć tymczasowy plik dla skryptu.

#!/bin/bash
exec 3>&0
exec <<-EOF 4>&0
BEGIN {print "HALLO"}
{print \$1}
EOF
gawk --re-interval -f <(cat 0>&4) 0>&3

Jedna rzecz jest w tym denerwująca: powłoka wykonuje w skrypcie rozwinięcie zmiennych, więc musisz zacytować każdy $ (tak jak to zrobiono w drugiej linii skryptu) i prawdopodobnie więcej.

Hans-Peter Störr
źródło
-1

W przypadku rozwiązania przenośnego użyj awkzamiast gawkwywoływać standardową powłokę BOURNE ( /bin/sh) za pomocą swojego shebang i wywołaj awkbezpośrednio, przekazując program w wierszu poleceń jako dokument tutaj zamiast przez stdin:

#!/bin/sh
gawk --re-interval <<<EOF
PROGRAM HERE
EOF

Uwaga: brak -fargumentu do awk. To pozostawia stdindostępne awkdo odczytania danych wejściowych. Zakładając, że gawkzainstalowałeś i na swoim PATH, daje to wszystko, co myślę, że próbowałeś zrobić z oryginalnym przykładem (zakładając, że chcesz, aby zawartość pliku była skryptem awk, a nie danymi wejściowymi, co myślę, że twoje podejście shebang potraktowałoby to jako ).

lharper71
źródło
3
To mi się nie udało. Człowiek bash mówi <<< blabla ustawia blabla na stdin. Czy chodziło Ci o << - EOF? Tak czy inaczej, powoduje to również przełączenie programu na stdin.
Hans-Peter Störr