Meaning of "exec tail -n +3 $0" line in 40_custom file
Solution 1:
The trick is what exec
does:
$ 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.
This means it will replace the shell with whatever is given to exec
, in this case tail
. Here's an example of it in action:
$ cat ~/foo.sh
#!/bin/sh
exec tail -n +3 "$0"
echo foo
$ ./foo.sh
echo foo
So the echo
command isn't executed since we have changed the shell and are using tail
instead. If we remove the exec tail
:
$ cat ~/foo.sh
#!/bin/sh
echo foo
$ ./foo.sh
foo
So, this is a neat trick that lets you write a script whose only job is to output its own contents. Presumably, whatever calls 40_custom
expects its contents as output. Of course, this begs the question of why not just running tail -n +3 /etc/grub.d/40_custom
directly.
I am guessing the answer is because grub
uses its own scripting language and this makes it need this workaround.
Solution 2:
The directory /etc/grub.d/
contains many executables (typically shell scripts, but any other executable type is also possible). Whenever grub-mkconfig
gets executed (e.g. if you run update-grub
, but also when you install an updated kernel package, which usually has a post-install hook that tells the package manager to update grub.cfg
), they are all executed in alphabetical order. Their outputs all get concatenated, and end up in the file /boot/grub/grub.cfg
, with neat section headers that show which part comes from which /etc/grub.d/
file.
This one particular file 40_custom
is designed to allow you to easily add entries/lines into grub.cfg
by simply typing/pasting them into this file. Other scripts in the same directory do more complex tasks like looking for kernels or non-linux operating systems and creating menu entries for them.
In order to allow grub-mkconfig
to treat all those files in the same way (execute and take the output), 40_custom
is a script and uses this exec tail -n +3 $0
mechanism to output its contents (minus the "header"). If it weren't an executable, update-grub
would need a special hard-coded exception to take this file's literal text content instead of executing it like all the others. But then what if you (or the makers of another linux distribution) want to give this file a different name? Or what if you didn't know about the exception and created a shell script named 40_custom
?
You can read more about grub-mkconfig
and /etc/grub.d/*
in the GNU GRUB Manual (although it talks mostly about options you can set in /etc/default/grub
), and there should also be a file /etc/grub.d/README
that states that these files get executed to form grub.cfg
.
Solution 3:
TL;DR: it's a trick to simplify adding new entries to the file
The whole point is described in one of the Ubuntu's Wiki pages on grub:
- Only executable files generate output to grub.cfg during execution of update-grub.
Output of scripts in /etc/grub.d/
becomes the contents of grub.cfg
file.
Now, what does exec
do ? It can either re-wire output for entire script or if a command is provided - the mentioned command overtakes and replaces the script process. What was once shell script with PID 1234 now is tail
command with PID 1234.
Now you already know that tail -n +3 $0
prints everything after the 3rd line in the script itself. So why do we need to do this ? If grub only cares about the output we could just as well do
cat <<EOF
menuentry {
...
}
EOF
In fact, you will find cat <<EOF
example in Fedora documentation, albeit for a different purpose. The whole point is in the comments - ease of use for the users:
# This file provides an easy way to add custom menu entries. Simply type the
# menu entries you want to add after this comment.
With exec
trick, you don't have to know what does cat <<EOF
do (spoiler, that's called here-doc), nor you have to remember to add the EOF
on the last line. Just add menuentry to the file and be done with it. Plus, if you're scripting adding a menuentry, you can simply append via >>
in shell to this file.
See also:
- What logic does the command “exec tail -n +3 $0” from grub2 config have?