Meaning of “exec tail -n +3 $0” line in 40_custom file
I am trying to understand the grub config files. So, during this process I came across with file /etc/grub.d/40_custom. My file contains the following lines:
#!/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
}
as my system is dual boot and apparently this is the boot loader for windows 10.
My question though is this part exec tail -n +3 $0
.
If I am deciphering it correctly this just means print the last lines starting from the 3rd line (+3
) of the file $0
. $0
of course in this case is the actual file /etc/grub.d/40_custom.
So, why do we use this command in 40_custom file? As I get it the output would be the same if ιt was omitted altogether. The only different I might think of is the 1st line which identifies the interpreter:
#!/bin/sh
But then again it is executed since exec tail -n +3 $0
follows it. So, is this just a (useless) convention?
grub2
add a comment |
I am trying to understand the grub config files. So, during this process I came across with file /etc/grub.d/40_custom. My file contains the following lines:
#!/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
}
as my system is dual boot and apparently this is the boot loader for windows 10.
My question though is this part exec tail -n +3 $0
.
If I am deciphering it correctly this just means print the last lines starting from the 3rd line (+3
) of the file $0
. $0
of course in this case is the actual file /etc/grub.d/40_custom.
So, why do we use this command in 40_custom file? As I get it the output would be the same if ιt was omitted altogether. The only different I might think of is the 1st line which identifies the interpreter:
#!/bin/sh
But then again it is executed since exec tail -n +3 $0
follows it. So, is this just a (useless) convention?
grub2
add a comment |
I am trying to understand the grub config files. So, during this process I came across with file /etc/grub.d/40_custom. My file contains the following lines:
#!/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
}
as my system is dual boot and apparently this is the boot loader for windows 10.
My question though is this part exec tail -n +3 $0
.
If I am deciphering it correctly this just means print the last lines starting from the 3rd line (+3
) of the file $0
. $0
of course in this case is the actual file /etc/grub.d/40_custom.
So, why do we use this command in 40_custom file? As I get it the output would be the same if ιt was omitted altogether. The only different I might think of is the 1st line which identifies the interpreter:
#!/bin/sh
But then again it is executed since exec tail -n +3 $0
follows it. So, is this just a (useless) convention?
grub2
I am trying to understand the grub config files. So, during this process I came across with file /etc/grub.d/40_custom. My file contains the following lines:
#!/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
}
as my system is dual boot and apparently this is the boot loader for windows 10.
My question though is this part exec tail -n +3 $0
.
If I am deciphering it correctly this just means print the last lines starting from the 3rd line (+3
) of the file $0
. $0
of course in this case is the actual file /etc/grub.d/40_custom.
So, why do we use this command in 40_custom file? As I get it the output would be the same if ιt was omitted altogether. The only different I might think of is the 1st line which identifies the interpreter:
#!/bin/sh
But then again it is executed since exec tail -n +3 $0
follows it. So, is this just a (useless) convention?
grub2
grub2
asked yesterday
EyprosEypros
1838
1838
add a comment |
add a comment |
3 Answers
3
active
oldest
votes
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.
Nice answer! But what if we'll write#!/bin/tail -n +2
as a shellbang? Will it print rest of the file?
– val
yesterday
@val try it and see :) Turns out it doesn't work perfectly as a shebang, the-n +2
seems to be interpreted as-n 2
and only the last two lines are printed. That's probably worth its own question though.
– terdon♦
yesterday
3
@val ...shebang behavior is a lot less standardized and portable than you may expect. See the "interpretation of the command arguments" section of en.wikipedia.org/wiki/Shebang_(Unix)#Portability
– Charles Duffy
yesterday
@val That works in Linux if you remove the space between then
and the+
. In Linux at least, after the executable path and a space, the following characters are treated as a single argument. So with the space, tail sees it and probably decides to remove whatever invalid prefix-n
's argument has (in this case, a space and a+
) until it gets to the number. However, like Charles Duffy said, the current way they did this is probably more portable to other Unices.
– JoL
22 hours ago
Why can't this just use a heredoc? Or a cat hashbang?
– fluffy
17 hours ago
|
show 2 more comments
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
.
New contributor
1
While terdon's answer explains how the line works, this one gives a more plausible design reason as for why GRUB does things this way.
– JoL
21 hours ago
Great answer. To your point about special exceptions - a "simpler" exception could have been having two directories e.g./etc/grub.d/exec
for exectuable files/scripts, and/etc/grub.d/static
for plain text files, or some other indicator to distinguish these. However, this was the design decision that they went with.
– Stobor
14 hours ago
add a comment |
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?
But why not have grub read the files directly? Do you have any idea why they chose to make them executable instead? It would be much simpler to have them as simple text files which are read by whatever process generates the menu, but instead they chose to make them executable and added this (neat) but complex workaround. Is it because grub has its own scripting language as I posit in my answer?
– terdon♦
yesterday
@terdon I don't think this has to do anything with the grub language itself. You wouldn't need#!/bin/sh
in that case. I suspect this is simply historical reason (carried over from PUPA codebase ) and a design decision inspired by SysV type of scripting.
– Sergiy Kolodyazhnyy
yesterday
@terdon This actually inspired a question: unix.stackexchange.com/q/492966/85039
– Sergiy Kolodyazhnyy
yesterday
add a comment |
Your Answer
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "89"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2faskubuntu.com%2fquestions%2f1107668%2fmeaning-of-exec-tail-n-3-0-line-in-40-custom-file%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
3 Answers
3
active
oldest
votes
3 Answers
3
active
oldest
votes
active
oldest
votes
active
oldest
votes
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.
Nice answer! But what if we'll write#!/bin/tail -n +2
as a shellbang? Will it print rest of the file?
– val
yesterday
@val try it and see :) Turns out it doesn't work perfectly as a shebang, the-n +2
seems to be interpreted as-n 2
and only the last two lines are printed. That's probably worth its own question though.
– terdon♦
yesterday
3
@val ...shebang behavior is a lot less standardized and portable than you may expect. See the "interpretation of the command arguments" section of en.wikipedia.org/wiki/Shebang_(Unix)#Portability
– Charles Duffy
yesterday
@val That works in Linux if you remove the space between then
and the+
. In Linux at least, after the executable path and a space, the following characters are treated as a single argument. So with the space, tail sees it and probably decides to remove whatever invalid prefix-n
's argument has (in this case, a space and a+
) until it gets to the number. However, like Charles Duffy said, the current way they did this is probably more portable to other Unices.
– JoL
22 hours ago
Why can't this just use a heredoc? Or a cat hashbang?
– fluffy
17 hours ago
|
show 2 more comments
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.
Nice answer! But what if we'll write#!/bin/tail -n +2
as a shellbang? Will it print rest of the file?
– val
yesterday
@val try it and see :) Turns out it doesn't work perfectly as a shebang, the-n +2
seems to be interpreted as-n 2
and only the last two lines are printed. That's probably worth its own question though.
– terdon♦
yesterday
3
@val ...shebang behavior is a lot less standardized and portable than you may expect. See the "interpretation of the command arguments" section of en.wikipedia.org/wiki/Shebang_(Unix)#Portability
– Charles Duffy
yesterday
@val That works in Linux if you remove the space between then
and the+
. In Linux at least, after the executable path and a space, the following characters are treated as a single argument. So with the space, tail sees it and probably decides to remove whatever invalid prefix-n
's argument has (in this case, a space and a+
) until it gets to the number. However, like Charles Duffy said, the current way they did this is probably more portable to other Unices.
– JoL
22 hours ago
Why can't this just use a heredoc? Or a cat hashbang?
– fluffy
17 hours ago
|
show 2 more comments
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 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.
edited yesterday
answered yesterday
terdon♦terdon
64.9k12137217
64.9k12137217
Nice answer! But what if we'll write#!/bin/tail -n +2
as a shellbang? Will it print rest of the file?
– val
yesterday
@val try it and see :) Turns out it doesn't work perfectly as a shebang, the-n +2
seems to be interpreted as-n 2
and only the last two lines are printed. That's probably worth its own question though.
– terdon♦
yesterday
3
@val ...shebang behavior is a lot less standardized and portable than you may expect. See the "interpretation of the command arguments" section of en.wikipedia.org/wiki/Shebang_(Unix)#Portability
– Charles Duffy
yesterday
@val That works in Linux if you remove the space between then
and the+
. In Linux at least, after the executable path and a space, the following characters are treated as a single argument. So with the space, tail sees it and probably decides to remove whatever invalid prefix-n
's argument has (in this case, a space and a+
) until it gets to the number. However, like Charles Duffy said, the current way they did this is probably more portable to other Unices.
– JoL
22 hours ago
Why can't this just use a heredoc? Or a cat hashbang?
– fluffy
17 hours ago
|
show 2 more comments
Nice answer! But what if we'll write#!/bin/tail -n +2
as a shellbang? Will it print rest of the file?
– val
yesterday
@val try it and see :) Turns out it doesn't work perfectly as a shebang, the-n +2
seems to be interpreted as-n 2
and only the last two lines are printed. That's probably worth its own question though.
– terdon♦
yesterday
3
@val ...shebang behavior is a lot less standardized and portable than you may expect. See the "interpretation of the command arguments" section of en.wikipedia.org/wiki/Shebang_(Unix)#Portability
– Charles Duffy
yesterday
@val That works in Linux if you remove the space between then
and the+
. In Linux at least, after the executable path and a space, the following characters are treated as a single argument. So with the space, tail sees it and probably decides to remove whatever invalid prefix-n
's argument has (in this case, a space and a+
) until it gets to the number. However, like Charles Duffy said, the current way they did this is probably more portable to other Unices.
– JoL
22 hours ago
Why can't this just use a heredoc? Or a cat hashbang?
– fluffy
17 hours ago
Nice answer! But what if we'll write
#!/bin/tail -n +2
as a shellbang? Will it print rest of the file?– val
yesterday
Nice answer! But what if we'll write
#!/bin/tail -n +2
as a shellbang? Will it print rest of the file?– val
yesterday
@val try it and see :) Turns out it doesn't work perfectly as a shebang, the
-n +2
seems to be interpreted as -n 2
and only the last two lines are printed. That's probably worth its own question though.– terdon♦
yesterday
@val try it and see :) Turns out it doesn't work perfectly as a shebang, the
-n +2
seems to be interpreted as -n 2
and only the last two lines are printed. That's probably worth its own question though.– terdon♦
yesterday
3
3
@val ...shebang behavior is a lot less standardized and portable than you may expect. See the "interpretation of the command arguments" section of en.wikipedia.org/wiki/Shebang_(Unix)#Portability
– Charles Duffy
yesterday
@val ...shebang behavior is a lot less standardized and portable than you may expect. See the "interpretation of the command arguments" section of en.wikipedia.org/wiki/Shebang_(Unix)#Portability
– Charles Duffy
yesterday
@val That works in Linux if you remove the space between the
n
and the +
. In Linux at least, after the executable path and a space, the following characters are treated as a single argument. So with the space, tail sees it and probably decides to remove whatever invalid prefix -n
's argument has (in this case, a space and a +
) until it gets to the number. However, like Charles Duffy said, the current way they did this is probably more portable to other Unices.– JoL
22 hours ago
@val That works in Linux if you remove the space between the
n
and the +
. In Linux at least, after the executable path and a space, the following characters are treated as a single argument. So with the space, tail sees it and probably decides to remove whatever invalid prefix -n
's argument has (in this case, a space and a +
) until it gets to the number. However, like Charles Duffy said, the current way they did this is probably more portable to other Unices.– JoL
22 hours ago
Why can't this just use a heredoc? Or a cat hashbang?
– fluffy
17 hours ago
Why can't this just use a heredoc? Or a cat hashbang?
– fluffy
17 hours ago
|
show 2 more comments
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
.
New contributor
1
While terdon's answer explains how the line works, this one gives a more plausible design reason as for why GRUB does things this way.
– JoL
21 hours ago
Great answer. To your point about special exceptions - a "simpler" exception could have been having two directories e.g./etc/grub.d/exec
for exectuable files/scripts, and/etc/grub.d/static
for plain text files, or some other indicator to distinguish these. However, this was the design decision that they went with.
– Stobor
14 hours ago
add a comment |
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
.
New contributor
1
While terdon's answer explains how the line works, this one gives a more plausible design reason as for why GRUB does things this way.
– JoL
21 hours ago
Great answer. To your point about special exceptions - a "simpler" exception could have been having two directories e.g./etc/grub.d/exec
for exectuable files/scripts, and/etc/grub.d/static
for plain text files, or some other indicator to distinguish these. However, this was the design decision that they went with.
– Stobor
14 hours ago
add a comment |
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
.
New contributor
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
.
New contributor
edited yesterday
New contributor
answered yesterday
Hans-JakobHans-Jakob
812
812
New contributor
New contributor
1
While terdon's answer explains how the line works, this one gives a more plausible design reason as for why GRUB does things this way.
– JoL
21 hours ago
Great answer. To your point about special exceptions - a "simpler" exception could have been having two directories e.g./etc/grub.d/exec
for exectuable files/scripts, and/etc/grub.d/static
for plain text files, or some other indicator to distinguish these. However, this was the design decision that they went with.
– Stobor
14 hours ago
add a comment |
1
While terdon's answer explains how the line works, this one gives a more plausible design reason as for why GRUB does things this way.
– JoL
21 hours ago
Great answer. To your point about special exceptions - a "simpler" exception could have been having two directories e.g./etc/grub.d/exec
for exectuable files/scripts, and/etc/grub.d/static
for plain text files, or some other indicator to distinguish these. However, this was the design decision that they went with.
– Stobor
14 hours ago
1
1
While terdon's answer explains how the line works, this one gives a more plausible design reason as for why GRUB does things this way.
– JoL
21 hours ago
While terdon's answer explains how the line works, this one gives a more plausible design reason as for why GRUB does things this way.
– JoL
21 hours ago
Great answer. To your point about special exceptions - a "simpler" exception could have been having two directories e.g.
/etc/grub.d/exec
for exectuable files/scripts, and /etc/grub.d/static
for plain text files, or some other indicator to distinguish these. However, this was the design decision that they went with.– Stobor
14 hours ago
Great answer. To your point about special exceptions - a "simpler" exception could have been having two directories e.g.
/etc/grub.d/exec
for exectuable files/scripts, and /etc/grub.d/static
for plain text files, or some other indicator to distinguish these. However, this was the design decision that they went with.– Stobor
14 hours ago
add a comment |
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?
But why not have grub read the files directly? Do you have any idea why they chose to make them executable instead? It would be much simpler to have them as simple text files which are read by whatever process generates the menu, but instead they chose to make them executable and added this (neat) but complex workaround. Is it because grub has its own scripting language as I posit in my answer?
– terdon♦
yesterday
@terdon I don't think this has to do anything with the grub language itself. You wouldn't need#!/bin/sh
in that case. I suspect this is simply historical reason (carried over from PUPA codebase ) and a design decision inspired by SysV type of scripting.
– Sergiy Kolodyazhnyy
yesterday
@terdon This actually inspired a question: unix.stackexchange.com/q/492966/85039
– Sergiy Kolodyazhnyy
yesterday
add a comment |
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?
But why not have grub read the files directly? Do you have any idea why they chose to make them executable instead? It would be much simpler to have them as simple text files which are read by whatever process generates the menu, but instead they chose to make them executable and added this (neat) but complex workaround. Is it because grub has its own scripting language as I posit in my answer?
– terdon♦
yesterday
@terdon I don't think this has to do anything with the grub language itself. You wouldn't need#!/bin/sh
in that case. I suspect this is simply historical reason (carried over from PUPA codebase ) and a design decision inspired by SysV type of scripting.
– Sergiy Kolodyazhnyy
yesterday
@terdon This actually inspired a question: unix.stackexchange.com/q/492966/85039
– Sergiy Kolodyazhnyy
yesterday
add a comment |
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?
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?
edited yesterday
answered yesterday
Sergiy KolodyazhnyySergiy Kolodyazhnyy
70.1k9144307
70.1k9144307
But why not have grub read the files directly? Do you have any idea why they chose to make them executable instead? It would be much simpler to have them as simple text files which are read by whatever process generates the menu, but instead they chose to make them executable and added this (neat) but complex workaround. Is it because grub has its own scripting language as I posit in my answer?
– terdon♦
yesterday
@terdon I don't think this has to do anything with the grub language itself. You wouldn't need#!/bin/sh
in that case. I suspect this is simply historical reason (carried over from PUPA codebase ) and a design decision inspired by SysV type of scripting.
– Sergiy Kolodyazhnyy
yesterday
@terdon This actually inspired a question: unix.stackexchange.com/q/492966/85039
– Sergiy Kolodyazhnyy
yesterday
add a comment |
But why not have grub read the files directly? Do you have any idea why they chose to make them executable instead? It would be much simpler to have them as simple text files which are read by whatever process generates the menu, but instead they chose to make them executable and added this (neat) but complex workaround. Is it because grub has its own scripting language as I posit in my answer?
– terdon♦
yesterday
@terdon I don't think this has to do anything with the grub language itself. You wouldn't need#!/bin/sh
in that case. I suspect this is simply historical reason (carried over from PUPA codebase ) and a design decision inspired by SysV type of scripting.
– Sergiy Kolodyazhnyy
yesterday
@terdon This actually inspired a question: unix.stackexchange.com/q/492966/85039
– Sergiy Kolodyazhnyy
yesterday
But why not have grub read the files directly? Do you have any idea why they chose to make them executable instead? It would be much simpler to have them as simple text files which are read by whatever process generates the menu, but instead they chose to make them executable and added this (neat) but complex workaround. Is it because grub has its own scripting language as I posit in my answer?
– terdon♦
yesterday
But why not have grub read the files directly? Do you have any idea why they chose to make them executable instead? It would be much simpler to have them as simple text files which are read by whatever process generates the menu, but instead they chose to make them executable and added this (neat) but complex workaround. Is it because grub has its own scripting language as I posit in my answer?
– terdon♦
yesterday
@terdon I don't think this has to do anything with the grub language itself. You wouldn't need
#!/bin/sh
in that case. I suspect this is simply historical reason (carried over from PUPA codebase ) and a design decision inspired by SysV type of scripting.– Sergiy Kolodyazhnyy
yesterday
@terdon I don't think this has to do anything with the grub language itself. You wouldn't need
#!/bin/sh
in that case. I suspect this is simply historical reason (carried over from PUPA codebase ) and a design decision inspired by SysV type of scripting.– Sergiy Kolodyazhnyy
yesterday
@terdon This actually inspired a question: unix.stackexchange.com/q/492966/85039
– Sergiy Kolodyazhnyy
yesterday
@terdon This actually inspired a question: unix.stackexchange.com/q/492966/85039
– Sergiy Kolodyazhnyy
yesterday
add a comment |
Thanks for contributing an answer to Ask Ubuntu!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2faskubuntu.com%2fquestions%2f1107668%2fmeaning-of-exec-tail-n-3-0-line-in-40-custom-file%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown