Meaning of "exec tail -n +3 $0" line in 40_custom file

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.


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.


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:

  1. 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?

Tags:

Grub2