Bedeutung der Zeile "exec tail -n +3 $ 0" in der Datei 40_custom


17

Ich versuche, die Grub-Konfigurationsdateien zu verstehen. Während dieses Prozesses bin ich auf die Datei /etc/grub.d/40_custom gestoßen . Meine Datei enthält die folgenden Zeilen:

#!/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
}

da mein System Dual Boot ist und dies anscheinend der Bootloader für Windows 10 ist.

Meine Frage ist jedoch dieser Teil exec tail -n +3 $0.
Wenn ich es richtig entschlüssele, bedeutet dies nur, dass die letzten Zeilen ab der dritten Zeile ( +3) der Datei gedruckt werden $0. $0in diesem fall ist natürlich die aktuelle datei /etc/grub.d/40_custom .

Warum verwenden wir diesen Befehl in der Datei 40_custom ? Wie ich es erhalte, wäre die Ausgabe die gleiche, wenn es ganz weggelassen wurde. Der einzige Unterschied, an den ich denken könnte, ist die erste Zeile, die den Interpreter identifiziert:

#!/bin/sh

Aber dann wird es wieder ausgeführt, da es exec tail -n +3 $0folgt. Also, ist das nur eine (nutzlose) Konvention?

Antworten:


16

Der Trick ist, was execmacht:

$ 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.

Dies bedeutet exec, dass in diesem Fall die Shell durch das ersetzt wird, was auch immer gegeben wird tail. Hier ist ein Beispiel in Aktion:

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

$ ./foo.sh
echo foo

Der echoBefehl wird also nicht ausgeführt, da wir die Shell geändert haben und tailstattdessen verwenden. Wenn wir das entfernen exec tail:

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

Dies ist also ein toller Trick, mit dem Sie ein Skript schreiben können, dessen einzige Aufgabe darin besteht, seinen eigenen Inhalt auszugeben. Vermutlich 40_customerwartet jeder Aufruf seinen Inhalt als Ausgabe. Das wirft natürlich die Frage auf, warum man nicht einfach tail -n +3 /etc/grub.d/40_customdirekt rennt .

Ich vermute, die Antwort ist, weil es grub seine eigene Skriptsprache verwendet und deshalb diese Problemumgehung benötigt.


Gute Antwort! Aber was ist, wenn wir #!/bin/tail -n +2als Shellbang schreiben ? Wird der Rest der Datei gedruckt?
Val sagt Reinstate Monica

@val probier es aus und sieh :) Es hat sich herausgestellt, dass es nicht perfekt als Shebang funktioniert, das -n +2scheint so interpretiert zu werden -n 2und nur die letzten beiden Zeilen werden gedruckt. Das ist aber wahrscheinlich eine eigene Frage wert.
Terdon

3
@val ... shebang Verhalten ist viel weniger standardisiert und portabel als Sie vielleicht erwarten. Siehe den Abschnitt "Interpretation der Befehlsargumente" von en.wikipedia.org/wiki/Shebang_(Unix)#Portability
Charles Duffy,

@val Das funktioniert unter Linux, wenn Sie das Leerzeichen zwischen nund entfernen +. Zumindest in Linux werden nach dem ausführbaren Pfad und einem Leerzeichen die folgenden Zeichen als ein einziges Argument behandelt. Daher erkennt tail das Leerzeichen und entscheidet sich wahrscheinlich, das -nArgument eines ungültigen Präfixes (in diesem Fall ein Leerzeichen und ein +) zu entfernen, bis die Zahl erreicht ist. Wie Charles Duffy sagte, ist die derzeitige Art und Weise, wie sie dies taten, wahrscheinlich für andere Unices portabler.
JoL

1
@fluffy Sie können hier doc verwenden. Siehe den Link aus der Fedora-Dokumentation in meiner Antwort. Sie nutzen genau cat <<EOF ...EOFdort
Sergiy Kolodyazhnyy

9

Das Verzeichnis /etc/grub.d/enthält viele ausführbare Dateien (normalerweise Shell-Skripte, aber auch jeder andere ausführbare Typ ist möglich). Wann grub-mkconfigimmer ausgeführt wird (z. B. wenn Sie ausführen update-grub, aber auch wenn Sie ein aktualisiertes Kernelpaket installieren, das normalerweise einen Post-Install-Hook hat , der den Paketmanager zum Aktualisieren auffordert grub.cfg), werden alle in alphabetischer Reihenfolge ausgeführt. Ihre Ausgaben werden alle verkettet und landen in der Datei /boot/grub/grub.cfg. Ordentliche Abschnittsüberschriften zeigen, welcher Teil aus welcher /etc/grub.d/Datei stammt.

Mit dieser einen bestimmten Datei 40_customkönnen Sie Einträge / Zeilen einfach hinzufügen, grub.cfgindem Sie sie in diese Datei eingeben / einfügen. Andere Skripte im selben Verzeichnis erledigen komplexere Aufgaben wie das Suchen nach Kernels oder Nicht-Linux-Betriebssystemen und das Erstellen von Menüeinträgen für diese.

Um grub-mkconfigalle diese Dateien gleich behandeln zu können (ausführen und die Ausgabe übernehmen), 40_customist ein Skript und verwendet diesen exec tail -n +3 $0Mechanismus, um seinen Inhalt auszugeben (abzüglich des "Headers"). Wenn es sich nicht um eine ausführbare Datei handeln update-grubwürde , wäre eine spezielle, fest codierte Ausnahme erforderlich, um den wörtlichen Textinhalt dieser Datei zu übernehmen, anstatt sie wie alle anderen Dateien auszuführen. Aber was ist, wenn Sie (oder die Hersteller einer anderen Linux-Distribution) dieser Datei einen anderen Namen geben möchten? Oder was ist, wenn Sie die Ausnahme nicht kannten und ein Shell-Skript mit dem Namen erstellt haben 40_custom?

Sie können mehr über grub-mkconfigund /etc/grub.d/*im GNU GRUB-Handbuch lesen (obwohl es hauptsächlich um Optionen geht, die Sie einstellen können /etc/default/grub), und es sollte auch eine Datei geben /etc/grub.d/README, die besagt, dass diese Dateien ausgeführt werden, um zu formen grub.cfg.


1
Während Terdons Antwort erklärt, wie die Linie funktioniert, gibt diese einen plausibleren Designgrund an, warum GRUB die Dinge so macht.
JoL

Gute Antwort. /etc/grub.d/execWas besondere Ausnahmen betrifft - eine "einfachere" Ausnahme könnte darin bestanden haben, zwei Verzeichnisse zu haben, z. B. für ausführbare Dateien / Skripte und /etc/grub.d/staticfür Nur- Text-Dateien oder einen anderen Indikator, um diese zu unterscheiden. Dies war jedoch die Designentscheidung, mit der sie gingen.
Stobor

3

TL; DR : Dies ist ein Trick, um das Hinzufügen neuer Einträge zur Datei zu vereinfachen

Der ganze Punkt ist in einer der Ubuntu-Wiki-Seiten auf grub beschrieben :

  1. Während der Ausführung von update-grub werden nur ausführbare Dateien in die Datei grub.cfg ausgegeben.

Die Ausgabe von Skripten in /etc/grub.d/wird zum Inhalt der grub.cfgDatei.

Nun, was hat zu exectun? Es kann entweder die Ausgabe für das gesamte Skript neu verkabeln oder, wenn ein Befehl angegeben wird, den Skriptprozess durch den genannten Befehl ersetzen. Was früher ein Shell-Skript mit PID 1234 war, ist jetzt ein tailBefehl mit PID 1234.

Jetzt wissen Sie bereits, dass tail -n +3 $0alles nach der 3. Zeile im Skript selbst gedruckt wird. Warum müssen wir das tun? Wenn sich grub nur um die Ausgabe kümmert, könnten wir das genauso gut tun

cat <<EOF
    menuentry {
    ...
    }
EOF

Tatsächlich finden Sie ein cat <<EOFBeispiel in der Fedora-Dokumentation , wenn auch zu einem anderen Zweck. Der springende Punkt ist in den Kommentaren - Benutzerfreundlichkeit für die Benutzer:

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

Mit execTrick müssen Sie nicht wissen, was zu cat <<EOFtun ist (Spoiler, das heißt hier-doc ), noch müssen Sie daran denken, das EOFin der letzten Zeile einzufügen. Fügen Sie der Datei einfach einen Menüeintrag hinzu, und fertig. Wenn Sie Skripte für das Hinzufügen eines Menüeintrags erstellen, können Sie >>diese Datei einfach in der Shell anhängen .

Siehe auch:


Aber warum hat grub die Dateien nicht direkt gelesen? Haben Sie eine Idee, warum sie sich dafür entschieden haben, sie stattdessen ausführbar zu machen? Es wäre viel einfacher, sie als einfache Textdateien zu haben, die von jedem Prozess gelesen werden, der das Menü generiert. Stattdessen haben sie diese ausführbar gemacht und diese (ordentliche), aber komplexe Problemumgehung hinzugefügt. Liegt es daran, dass grub eine eigene Skriptsprache hat, wie ich in meiner Antwort postuliere?
Terdon

@terdon Ich glaube nicht, dass dies irgendetwas mit der Grub-Sprache selbst zu tun hat. Das würdest du #!/bin/shin diesem Fall nicht brauchen . Ich vermute, dies ist einfach ein historischer Grund (übertragen von der PUPA- Codebasis) und eine Designentscheidung, die von der SysV-Art der Skripterstellung inspiriert ist.
Sergiy Kolodyazhnyy

@terdon Dies inspirierte tatsächlich eine Frage: unix.stackexchange.com/q/492966/85039
Sergiy Kolodyazhnyy
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.