40_custom文件中“ exec tail -n +3 $ 0”行的含义


17

我正在尝试了解grub配置文件。因此,在此过程中,我遇到了文件/etc/grub.d/40_custom。我的文件包含以下几行:

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

因为我的系统是双启动,显然这是Windows 10的启动加载程序。

我的问题是这部分exec tail -n +3 $0
如果我正确地解密了,这意味着从文件的第三行(+3)开始打印最后的行$0$0在这种情况下,当然是实际文件/etc/grub.d/40_custom

那么,为什么要在40_custom文件中使用此命令?如我所知,如果完全省略lt,输出将是相同的。我可能想到的唯一不同之处是标识解释器的第一行:

#!/bin/sh

但是随后再次执行它,因为exec tail -n +3 $0遵循它。那么,这仅仅是(无用的)约定吗?

Answers:


16

诀窍是什么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.

这意味着exec在这种情况下,它将使用给出的任何内容替换外壳tail。这是一个实际的例子:

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

$ ./foo.sh
echo foo

因此该echo命令未执行,因为我们已经更改了外壳并正在使用它tail。如果我们删除exec tail

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

因此,这是一个巧妙的技巧,可让您编写一个脚本,其唯一的工作就是输出其自己的内容。大概,任何调用都40_custom希望其内容作为输出。当然,这引出了为什么不直接运行的问题tail -n +3 /etc/grub.d/40_custom

我猜答案是因为grub 使用了自己的脚本语言,因此需要这种解决方法。


好答案!但是,如果我们将其写成#!/bin/tail -n +2shellbang怎么办?它会打印文件的其余部分吗?
瓦尔说莫妮卡

@val尝试一下,看看:)原来,它不能像shebang那样完美运行,-n +2似乎被解释为-n 2仅输出最后两行。不过,这可能值得提出自己的问题。

3
@val ... shebang行为比您预期的要标准化和可移植性低得多。请参阅en.wikipedia.org/wiki/Shebang_(Unix)#Portability
Charles Duffy

@val如果删除和之间的空格,则在Linux n中有效+。至少在Linux中,在可执行文件路径和空格之后,以下字符被视为单个参数。因此,有了空格,tail就会看到它,并可能决定删除任何无效前缀-n的参数(在本例中为空格和a +),直到达到数字为止。但是,就像查尔斯·达菲(Charles Duffy)所说的那样,目前他们这样做的方式可能比其他Unices更容易移植。
JoL

1
@fluffy他们可以在这里使用文档。请参阅我的答案中Fedora文档的链接。他们正是cat <<EOF ...EOF在这里使用
Sergiy Kolodyazhnyy

9

该目录/etc/grub.d/包含许多可执行文件(通常是shell脚本,但是其他任何可执行文件类型也是可能的)。每当grub-mkconfig执行时(例如,如果您运行update-grub,而且还安装了更新的内核程序包(通常具有告诉安装程序包管理器更新的安装后挂钩grub.cfg)),它们都将按字母顺序执行。它们的输出全部串联起来,并最终到达文件中/boot/grub/grub.cfg,并带有整洁的节标题,这些节标题显示了哪个部分来自哪个/etc/grub.d/文件。

这个特定的文件40_custom旨在使您可以grub.cfg通过简单地将条目/行键入/粘贴到该文件中来轻松地添加条目/行。同一目录中的其他脚本执行更复杂的任务,例如查找内核或非Linux操作系统并为其创建菜单项。

为了允许grub-mkconfig以相同的方式处理所有这些文件(执行并获取输出),40_custom是一个脚本,并使用此exec tail -n +3 $0机制输出其内容(减去“标头”)。如果不是可执行文件,则update-grub需要特殊的硬编码异常来获取此文件的原义文本内容,而不是像其他所有文件一样执行该文件。但是,如果您(或另一个Linux发行版的制造商)想要给该文件起一个不同的名字怎么办?或者,如果您不知道该异常并创建了一个名为Shell的脚本,该40_custom怎么办?

您可以在GNU GRUB手册中阅读更多内容grub-mkconfig,尽管它主要讨论可以设置的选项,但也应该有一个文件说明这些文件已被执行成form 。/etc/grub.d/*/etc/default/grub/etc/grub.d/READMEgrub.cfg


1
尽管terdon的回答解释了这条线是如何工作的,但这一解释为GRUB为什么以这种方式提供了一个更合理的设计原因。
JoL

好答案。就特殊异常而言,“简单”异常可能有两个目录,例如/etc/grub.d/exec可执行文件/脚本,/etc/grub.d/static纯文本文件或其他用于区分这些目录的指示符。但是,这是他们做出的设计决定。
Stobor

3

TL; DR:简化向文件添加新条目的技巧

在grubUbuntu维基页面之一中描述了全部内容

  1. 在执行update-grub期间,只有可执行文件会生成输出到grub.cfg。

中的脚本输出/etc/grub.d/成为grub.cfg文件的内容。

现在,该怎么exec办?它可以重新连接整个脚本的输出,或者如果提供了命令,则上述命令将取代并替换脚本过程。以前使用PID 1234的Shell脚本现在是tail使用PID 1234的命令。

现在,您已经知道该命令tail -n +3 $0将在脚本本身的第三行之后打印所有内容。那么为什么我们需要这样做呢?如果grub只关心输出,我们也可以这样做

cat <<EOF
    menuentry {
    ...
    }
EOF

实际上,尽管出于其他目的,您仍可以cat <<EOFFedora文档中找到示例。重点在于注释-用户的易用性:

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

使用exec技巧,您不必知道会cat <<EOF做什么(扰流器,称为here-doc),也不必记住EOF在最后一行添加。只需将menuentry添加到文件中并完成即可。另外,如果要编写脚本来添加菜单项,则只需>>在外壳程序中通过via附加到此文件即可。

也可以看看:


但是,为什么不让grub直接读取文件?您知道为什么他们选择使它们可执行吗?将它们作为简单的文本文件将更容易,可以通过任何生成菜单的过程读取它们,但是相反,他们选择使它们可执行,并添加了这种(简洁的)但复杂的解决方法。是因为我回答中提到的grub有自己的脚本语言吗?

@terdon我认为这与grub语言本身无关。#!/bin/sh在这种情况下,您将不需要。我怀疑这仅仅是历史原因(继承自PUPA代码库),并且是受SysV类型的脚本启发的设计决策。
Sergiy Kolodyazhnyy

@terdon这实际上激发了一个问题:unix.stackexchange.com/q/492966/85039
Sergiy Kolodyazhnyy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.