Dlaczego „-” w „#! / bin / sh - ”shebang?

28
#! / bin / sh -

Jest (lub przynajmniej był) często zalecanym shebangiem do interpretacji skryptu /bin/sh.

Dlaczego nie tylko #! /bin/shlub #!/bin/sh?

Co to jest -za?

Stéphane Chazelas
źródło

Odpowiedzi:

38

To z podobnego powodu, dla którego musisz pisać:

rm -- *.txt

I nie

rm *.txt

Chyba że możesz zagwarantować, że żaden .txtplik w bieżącym katalogu nie ma nazwy zaczynającej się od -.

W:

rm <arg>

<arg>jest uważane za opcję, jeśli zaczyna się od -lub plik do usunięcia w inny sposób. W

rm - <arg>

argjest zawsze uważany za plik do usunięcia, niezależnie od tego, czy zaczyna się od, -czy nie.

To samo dotyczy sh.

Kiedy wykonuje się skrypt, który zaczyna się od

#! /bin/sh

Zazwyczaj z:

execve("path/to/the-script", ["the-script", "arg"], [environ])

System przekształca go w:

execve("/bin/sh", ["/bin/sh", "path/to/the-script", "arg"], [environ])

path/to/the-scriptZwykle nie jest coś, co jest pod kontrolą autora skryptu. Autor nie jest w stanie przewidzieć, gdzie będą przechowywane kopie skryptu ani pod jaką nazwą. W szczególności nie mogą zagwarantować, path/to/the-scriptże nazwa, z którą się nazywa, nie zacznie się -(lub z +którym jest również problem sh). Dlatego trzeba- tutaj, aby zaznaczyć koniec opcji.

Na przykład w moim systemie zcat(podobnie jak większość innych skryptów) jest jeden przykład skryptu, który nie przestrzegał tego zalecenia:

$ head -n1 /bin/zcat
#!/bin/sh
$ mkdir +
$ ln -s /bin/zcat +/
$ +/zcat
/bin/sh: +/: invalid option
[...]

Teraz możesz zapytać dlaczego, #! /bin/sh -a nie #! /bin/sh --?

Chociaż #! /bin/sh --działałby z powłokami POSIX, #! /bin/sh -jest bardziej przenośny; w szczególności do starożytnych wersji sh. shtraktuje się -jak i koniec opcji wcześniej getopt()i ogólnie używa się --do oznaczania końca opcji przez długi czas. Sposób, w jaki powłoka Bourne'a (z późnych lat 70.) przeanalizowała swoje argumenty, tylko pierwszy argument był rozważany dla opcji, jeśli zaczął -. Wszystkie późniejsze znaki -będą traktowane jako nazwy opcji; jeśli po znaku nie było żadnej postaci -, nie było opcji. To utknęło i wszystkie późniejsze powłoki podobne do Bourne'a rozpoznają -jako sposób na oznaczenie końca opcji.

W powłoce Bourne'a (ale nie we współczesnych powłokach podobnych do Bourne'a) #! /bin/sh -eufrównież obejdzie ten problem, ponieważ rozważano tylko pierwszy argument dotyczący opcji.

Teraz można powiedzieć, że jesteśmy pedantyczni i dlatego też napisałem potrzebę kursywą powyżej:

  1. Nikt przy zdrowych zmysłach nie nazwałbym skrypt z czymś zaczynając -lub +lub umieścić je w katalogu, którego nazwa zaczyna się -lub +.
  2. nawet gdyby tak było, pierwszy argumentowałby, że mogą obwiniać samych siebie, ale także, gdy wywołujesz skrypt, najczęściej pochodzi on z powłoki lub z funkcji execvp()/ execlp()-type. I w takim przypadku zazwyczaj wywołujesz je albo w the-scriptcelu wyszukania, w $PATHktórym to przypadku argument ścieżki do execve()wywołania systemowego zwykle zaczyna się od /(nie -ani +), albo tak ./the-script, jakbyś chciał the-scripturuchomić w bieżącym katalogu (i wtedy ścieżka zaczyna się od ./, -ani +też).

Oprócz teoretycznej kwestii poprawności istnieje jeszcze jeden powód, dla którego #! /bin/sh -został zalecony jako dobra praktyka . A to sięga czasów, kiedy kilka systemów nadal obsługiwało skrypty setuid.

Jeśli masz skrypt, który zawiera:

#! /bin/sh
/bin/echo "I'm running as root"

I ten skrypt był setuid root (jak z -r-sr-xr-x root binuprawnieniami), w tych systemach, gdy jest wykonywany przez zwykłego użytkownika,

execve("/bin/sh", ["/bin/sh", "path/to/the-script"], [environ])

byłoby zrobione jak root!

Jeśli użytkownik utworzy dowiązanie symboliczne /tmp/-i -> path/to/the-scripti wykona je jako -i, uruchomi interaktywną powłokę ( /bin/sh -i) jako root.

-Byłoby obejść, że (nie byłoby obejść ten problem rasy warunek , albo fakt, że niektóre shimplementacje jak jakiś ksh88opartych o te byłyby Lookup argumentów skryptu bez /w $PATHchoć).

W dzisiejszych czasach prawie żaden system nie obsługuje już skryptu setuid, a niektóre z tych, które nadal działają (zwykle nie są domyślnie), kończą robienie execve("/bin/sh", ["/bin/sh", "/dev/fd/<n>", arg])(gdzie <n>deskryptor pliku jest otwarty do odczytu na skrypcie), który działa zarówno na ten problem, jak i na warunki wyścigu.

Zauważ, że masz podobne problemy z większością tłumaczy, nie tylko z powłokami Bourne'a. Powłoki inne niż Bourne na ogół nie obsługują -jako znacznika końca opcji, ale ogólnie obsługują --zamiast tego (przynajmniej w przypadku nowoczesnych wersji).

Również

#! /usr/bin/awk -f
#! /usr/bin/sed -f

Nie ma problemu, gdy Kolejnym argumentem jest traktowane jako argument do -fopcji w każdym razie, ale to nadal nie działa, jeśli path/to/scriptjest -(w tym przypadku, z większością sed/ awkimplementacjach sed/ awknie odczytać kod z -plik, ale zamiast standardowego wejścia).

Pamiętaj również, że w większości systemów nie można używać:

#! /usr/bin/env sh -
#! /usr/bin/perl -w --

Podobnie jak w większości systemów, mechanizm shebang pozwala tylko na jeden argument za ścieżką tłumacza.

To, czy użyć #! /bin/sh -vs #!/bin/sh -, to tylko kwestia gustu. Wolę ten pierwszy, ponieważ sprawia, że ​​ścieżka tłumacza jest bardziej widoczna i ułatwia wybór myszy. Istnieje legenda, która mówi, że przestrzeń była potrzebna w niektórych starożytnych wersjach Uniksa, ale AFAIK, która nigdy nie została zweryfikowana.

Bardzo dobre referencje na temat sznurka unixowego można znaleźć na stronie https://www.in-ulm.de/~mascheck/various/shebang

Stéphane Chazelas
źródło
1
Czy ma to jakieś znaczenie dla #! / Bin / bash?
Joe
1
@Joe, oczywiście, bashakceptuje opcje jak każdą inną powłokę. Będąc powłoką podobną do Bourne'a i POSIX, bashakceptuje zarówno #! /bin/bash -i #! /bin/bash --.
Stéphane Chazelas,