Znaczenie wiersza „exec tail -n +3 $ 0” w pliku 40_custom

17

Próbuję zrozumieć pliki konfiguracyjne grub. Podczas tego procesu natknąłem się na plik /etc/grub.d/40_custom . Mój plik zawiera następujące wiersze:

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
menuentry "Windows 10" --class windows --class os {
insmod part_msdos
savedefault
insmod ntfs
insmod ntldr
set root='(hd0,msdos1)'
ntldr ($root)/bootmgr
}

ponieważ mój system ma podwójny rozruch i najwyraźniej jest to program ładujący dla systemu Windows 10.

Moje pytanie dotyczy jednak tej części exec tail -n +3 $0.
Jeśli odszyfrowuję go poprawnie, oznacza to po prostu wydrukowanie ostatnich linii, zaczynając od 3. linii ( +3) pliku $0. $0oczywiście w tym przypadku jest to plik /etc/grub.d/40_custom .

Dlaczego więc używamy tego polecenia w pliku 40_custom ? Jak rozumiem, wynik byłby taki sam, gdyby ιt został całkowicie pominięty. Jedyne, o czym mógłbym pomyśleć, to pierwsza linia identyfikująca tłumacza:

#!/bin/sh

Ale potem znowu jest wykonywany, ponieważ exec tail -n +3 $0następuje po nim. Czy to tylko konwencja (bezużyteczna)?

Eypros
źródło

Odpowiedzi:

16

Sztuką jest to, co exec:

$ help exec
exec: exec [-cl] [-a name] [command [arguments ...]] [redirection ...]
    Replace the shell with the given command.

    Execute COMMAND, replacing this shell with the specified program.
    ARGUMENTS become the arguments to COMMAND.  If COMMAND is not specified,
    any redirections take effect in the current shell.

Oznacza to, że execw tym przypadku zastąpi powłokę tym, co zostało dane tail. Oto przykład tego w akcji:

$ cat ~/foo.sh
#!/bin/sh
exec tail -n +3 "$0"
echo foo

$ ./foo.sh
echo foo

Więc echopolecenie nie jest wykonywane, ponieważ zmieniliśmy powłokę i używamy tailzamiast tego. Jeśli usuniemy exec tail:

$ cat ~/foo.sh
#!/bin/sh
echo foo
$ ./foo.sh
foo

Jest to więc fajna sztuczka, która pozwala napisać skrypt, którego jedynym zadaniem jest wyprowadzenie własnej zawartości. Przypuszczalnie wszystkie wywołania 40_customoczekują, że ich zawartość zostanie wyświetlona. Oczywiście rodzi się pytanie, dlaczego nie po prostu biegać tail -n +3 /etc/grub.d/40_custombezpośrednio.

Zgaduję, że odpowiedź brzmi, ponieważ grub używa własnego języka skryptowego, a to wymaga tego obejścia.

terdon
źródło
Niezła odpowiedź! Ale co, jeśli napiszemy #!/bin/tail -n +2jako shellbang? Czy wydrukuje resztę pliku?
val mówi Przywróć Monikę
@val spróbuj i zobacz :) Okazuje się, że nie działa idealnie jako shebang, -n +2wydaje się być interpretowane jako -n 2i drukowane są tylko dwie ostatnie linie. To chyba warte własnego pytania.
terdon
3
@val ... zachowanie shebang jest o wiele mniej znormalizowane i przenośne, niż można się spodziewać. Zobacz sekcję „interpretacja argumentów polecenia” w en.wikipedia.org/wiki/Shebang_(Unix)#Portability
Charles Duffy
@val Działa w systemie Linux, jeśli usuniesz spację między ni +. Przynajmniej w systemie Linux, po ścieżce wykonywalnej i spacji, następujące znaki są traktowane jako pojedynczy argument. Tak więc w przypadku spacji ogon ją widzi i prawdopodobnie postanawia usunąć wszelkie niepoprawne -nargumenty prefiksu (w tym przypadku spację ia +), dopóki nie dojdzie do liczby. Jednak, jak powiedział Charles Duffy, obecny sposób, w jaki to zrobili, jest prawdopodobnie bardziej przenośny dla innych Unices.
JoL
1
@fluffy Oni mogą użyć tutaj doc. Zobacz link z dokumentacji Fedory w mojej odpowiedzi. Używają dokładnie cat <<EOF ...EOFtam
Sergiy Kolodyazhnyy
9

Katalog /etc/grub.d/zawiera wiele plików wykonywalnych (zwykle skrypty powłoki, ale możliwy jest także każdy inny typ pliku wykonywalnego). Ilekroć grub-mkconfigzostanie wykonany (np. Jeśli uruchomisz update-grub, ale także podczas instalowania zaktualizowanego pakietu jądra, który zwykle ma hak poinstalacyjny, który informuje menedżera pakietów o aktualizacji grub.cfg), wszystkie są wykonywane w kolejności alfabetycznej. Wszystkie dane wyjściowe są łączone i kończą się w pliku /boot/grub/grub.cfg, dzięki zgrabnym nagłówkom sekcji, które pokazują, która część pochodzi z którego /etc/grub.d/pliku.

Ten jeden konkretny plik 40_customumożliwia łatwe dodawanie wpisów / linii grub.cfg, po prostu wpisując / wklejając je do tego pliku. Inne skrypty w tym samym katalogu wykonują bardziej złożone zadania, takie jak wyszukiwanie jąder lub systemów operacyjnych innych niż Linux i tworzenie dla nich pozycji menu.

Aby umożliwić grub-mkconfigtraktowanie wszystkich tych plików w ten sam sposób (wykonaj i pobierz dane wyjściowe), 40_customjest skrypt i używa tego exec tail -n +3 $0mechanizmu do wyświetlania zawartości (pomniejszonej o „nagłówek”). Gdyby nie był to plik wykonywalny, update-grubpotrzebowałby specjalnego, zakodowanego na stałe wyjątku, aby wziąć dosłowną zawartość tekstową tego pliku zamiast wykonywać go tak jak wszystkie inne. Ale co jeśli ty (lub twórcy innej dystrybucji linuksa) chcesz nadać temu plikowi inną nazwę? A co jeśli nie wiedziałeś o wyjątku i utworzyłeś skrypt powłoki o nazwie 40_custom?

Możesz przeczytać więcej na temat grub-mkconfigi /etc/grub.d/*w Podręczniku GNU GRUB (chociaż mówi głównie o opcjach, które możesz ustawić /etc/default/grub), i powinien być również plik, /etc/grub.d/READMEktóry stwierdza, że ​​pliki te są wykonywane do postaci grub.cfg.

Hans-Jakob
źródło
1
Podczas gdy odpowiedź terdona wyjaśnia, jak działa linia, ta daje bardziej prawdopodobny powód projektowy, dlaczego GRUB robi to w ten sposób.
JoL
Świetna odpowiedź. Jeśli chodzi o specjalne wyjątki - „prostszym” wyjątkiem mogą być dwa katalogi, np. /etc/grub.d/execDla możliwych do wykonania plików / skryptów i /etc/grub.d/staticdla zwykłych plików tekstowych, lub jakiś inny wskaźnik umożliwiający ich rozróżnienie. Była to jednak decyzja projektowa, z którą poszli.
Stobor
3

TL; DR : sztuczka polega na uproszczeniu dodawania nowych wpisów do pliku

Cały punkt jest opisany na jednej ze stron Wiki Ubuntu na temat grub :

  1. Tylko pliki wykonywalne generują dane wyjściowe do pliku grub.cfg podczas wykonywania aktualizacji-grub.

Dane wyjściowe skryptów /etc/grub.d/stają się zawartością grub.cfgpliku.

Co teraz robi exec? Może albo ponownie przesłać dane wyjściowe dla całego skryptu, albo jeśli podano polecenie - wspomniane polecenie wyprzedza i zastępuje proces skryptu. To, co kiedyś było skryptem powłoki z PID 1234, teraz jest tailpoleceniem z PID 1234.

Teraz już wiesz, że tail -n +3 $0drukuje wszystko po 3 linii w samym skrypcie. Dlaczego więc musimy to zrobić? Gdyby grub dbał tylko o wynik, równie dobrze moglibyśmy to zrobić

cat <<EOF
    menuentry {
    ...
    }
EOF

W rzeczywistości znajdziesz cat <<EOFprzykład w dokumentacji Fedory , choć w innym celu. Chodzi o to w komentarzach - łatwość użytkowania dla użytkowników:

# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.

Dzięki exectrickowi nie musisz wiedzieć, co robi cat <<EOF(spoiler, to się nazywa tutaj-doc ), ani nie musisz pamiętać, aby dodać EOFw ostatnim wierszu. Po prostu dodaj menu do pliku i gotowe. Dodatkowo, jeśli piszesz skrypt, dodając menu, możesz po prostu dołączyć >>do tego pliku za pomocą powłoki.

Zobacz też:

Sergiy Kolodyazhnyy
źródło
Ale dlaczego grub nie odczytał plików bezpośrednio? Czy masz pojęcie, dlaczego zdecydowali się na wykonanie ich zamiast tego? Znacznie łatwiej byłoby mieć je jako proste pliki tekstowe, które są odczytywane przez dowolny proces generujący menu, ale zamiast tego zdecydowały się na wykonanie ich i dodały to (schludne), ale złożone obejście. Czy to dlatego, że grub ma swój własny język skryptowy, jak twierdzę w mojej odpowiedzi?
terdon
@terdon Nie sądzę, żeby miało to coś wspólnego z samym językiem grub. W takim przypadku nie byłbyś potrzebny #!/bin/sh. Podejrzewam, że jest to po prostu powód historyczny (przeniesiony z bazy kodu PUPA ) i decyzja projektowa zainspirowana skryptami typu SysV.
Sergiy Kolodyazhnyy
@terdon To właściwie zainspirowało pytanie: unix.stackexchange.com/q/492966/85039
Sergiy Kolodyazhnyy