Significado da linha "exec tail -n +3 $ 0" no arquivo 40_custom

17

Estou tentando entender os arquivos de configuração do grub. Então, durante esse processo, me deparei com o arquivo /etc/grub.d/40_custom . Meu arquivo contém as seguintes linhas:

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

como o meu sistema é de inicialização dupla e, aparentemente, este é o carregador de inicialização para o Windows 10.

Minha pergunta é essa parte exec tail -n +3 $0.
Se eu estiver decifrando corretamente, isso significa imprimir as últimas linhas a partir da 3ª linha ( +3) do arquivo $0. $0é claro que, neste caso, é o arquivo real /etc/grub.d/40_custom .

Então, por que usamos este comando no arquivo 40_custom ? Na minha opinião, a saída seria a mesma se fosse omitido por completo. A única diferença em que posso pensar é a 1ª linha que identifica o intérprete:

#!/bin/sh

Mas, novamente, é executado desde que o exec tail -n +3 $0segue. Então, isso é apenas uma convenção (inútil)?

Eypros
fonte

Respostas:

16

O truque é o que execfaz:

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

Isso significa que ele substituirá o shell pelo que for dado exec, neste caso tail. Aqui está um exemplo disso em ação:

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

$ ./foo.sh
echo foo

Portanto, o echocomando não é executado, pois alteramos o shell e estamos usando tail. Se removermos o exec tail:

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

Portanto, esse é um truque interessante que permite escrever um script cuja única tarefa é produzir seu próprio conteúdo. Presumivelmente, qualquer chamada 40_customespera que seu conteúdo seja produzido. Obviamente, isso levanta a questão de por que não apenas correr tail -n +3 /etc/grub.d/40_customdiretamente.

Eu acho que a resposta é porque grub usa sua própria linguagem de script e isso faz com que seja necessária essa solução alternativa.

Terdon
fonte
Boa resposta! Mas e se escrevermos #!/bin/tail -n +2como um shellbang? Irá imprimir o restante do arquivo?
val diz Reintegrar Monica
@val tente e veja :) Acontece que ele não funciona perfeitamente como um shebang, o que -n +2parece ser interpretado como -n 2e apenas as duas últimas linhas são impressas. Provavelmente isso vale sua própria pergunta.
terdon 7/01
3
@val ... o comportamento shebang é muito menos padronizado e portátil do que você imagina. Consulte a seção "interpretação dos argumentos de comando" em en.wikipedia.org/wiki/Shebang_(Unix)#Portability
Charles Duffy em
@val Isso funciona no Linux se você remover o espaço entre o ne o +. No Linux, pelo menos, após o caminho executável e um espaço, os seguintes caracteres são tratados como um único argumento. Portanto, com o espaço, o tail o vê e provavelmente decide remover qualquer -nargumento do prefixo inválido (neste caso, um espaço e a +) até chegar ao número. No entanto, como Charles Duffy disse, a maneira atual como eles fizeram isso é provavelmente mais portátil para outros Unices.
JoL
1
@ fofo Eles podem usar aqui doc. Veja o link da documentação do Fedora na minha resposta. Eles usam exatamente cat <<EOF ...EOF
Sergiy Kolodyazhnyy 08/01
9

O diretório /etc/grub.d/contém muitos executáveis ​​(normalmente shell scripts, mas qualquer outro tipo de executável também é possível). Sempre que grub-mkconfigé executado (por exemplo, se você executa update-grub, mas também quando instala um pacote atualizado do kernel, que geralmente possui um gancho pós-instalação que instrui o gerenciador de pacotes a atualizar grub.cfg), todos são executados em ordem alfabética. Todas as saídas são concatenadas e acabam no arquivo /boot/grub/grub.cfg, com cabeçalhos de seção organizados que mostram qual parte vem de qual /etc/grub.d/arquivo.

Este arquivo específico 40_customfoi projetado para permitir que você adicione entradas / linhas facilmente grub.cfg, simplesmente digitando / colando-as nesse arquivo. Outros scripts no mesmo diretório executam tarefas mais complexas, como procurar kernels ou sistemas operacionais não-linux e criar entradas de menu para eles.

Para permitir grub-mkconfigtratar todos esses arquivos da mesma maneira (executar e obter a saída), 40_customé um script e usa esse exec tail -n +3 $0mecanismo para gerar seu conteúdo (menos o "cabeçalho"). Se não fosse um executável, update-grubseria necessária uma exceção especial codificada para pegar o conteúdo de texto literal desse arquivo, em vez de executá-lo como todos os outros. Mas e se você (ou os fabricantes de outra distribuição Linux) quiser dar um nome diferente a esse arquivo? Ou, e se você não soubesse da exceção e criava um script de shell com o nome 40_custom?

Você pode ler mais sobre grub-mkconfige /etc/grub.d/*no Manual do GNU GRUB (embora ele fale principalmente sobre as opções que você pode definir /etc/default/grub), e também deve haver um arquivo /etc/grub.d/READMEinformando que esses arquivos são executados para formar grub.cfg.

Hans-Jakob
fonte
1
Embora a resposta de terdon explique como a linha funciona, esta fornece uma razão de projeto mais plausível para o GRUB fazer as coisas dessa maneira.
JoL
Ótima resposta. Para o seu ponto sobre exceções especiais - uma exceção "mais simples" poderia ter dois diretórios, por exemplo, /etc/grub.d/execarquivos / scripts executáveis ​​e /etc/grub.d/staticarquivos de texto sem formatação, ou algum outro indicador para diferenciá-los. No entanto, essa foi a decisão de design que eles adotaram.
Stobor
3

TL; DR : é um truque para simplificar a adição de novas entradas ao arquivo

O ponto todo é descrito em uma das páginas Wiki do Ubuntu no grub :

  1. Somente arquivos executáveis ​​geram saída para o grub.cfg durante a execução do update-grub.

A saída de scripts /etc/grub.d/torna-se o conteúdo do grub.cfgarquivo.

Agora, o que execfaz? Ele pode reconectar a saída de todo o script ou se um comando for fornecido - o comando mencionado ultrapassa e substitui o processo de script. O que antes era o script de shell com o PID 1234 agora é o tailcomando com o PID 1234.

Agora você já sabe que tail -n +3 $0imprime tudo após a terceira linha no próprio script. Então, por que precisamos fazer isso? Se o grub se importar apenas com a saída, poderíamos fazer o mesmo

cat <<EOF
    menuentry {
    ...
    }
EOF

De fato, você encontrará cat <<EOFexemplos na documentação do Fedora , embora para um propósito diferente. O ponto principal está nos comentários - facilidade de uso para os usuários:

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

Com exectruque, você não precisa saber o que cat <<EOFfaz (spoiler, que é chamado aqui-doc ), nem precisa se lembrar de adicionar EOFa última linha. Basta adicionar menuentry ao arquivo e pronto. Além disso, se você estiver adicionando uma entrada de menu ao script, basta anexar via >>shell neste arquivo.

Veja também:

Sergiy Kolodyazhnyy
fonte
Mas por que não o grub lê os arquivos diretamente? Você tem alguma idéia de por que eles escolheram torná-los executáveis? Seria muito mais simples tê-los como arquivos de texto simples, lidos por qualquer processo que gere o menu, mas eles optaram por torná-los executáveis ​​e adicionaram essa solução alternativa (pura) mas complexa. É porque o grub tem sua própria linguagem de script como afirmo na minha resposta?
terdon 7/01
@terdon Acho que isso não tem nada a ver com a linguagem grub. Você não precisaria #!/bin/shnesse caso. Suspeito que esse seja simplesmente um motivo histórico ( herdado da base de código PUPA ) e uma decisão de design inspirada no tipo de script SysV.
Sergiy Kolodyazhnyy 07/01
@terdon Isso realmente inspirou uma pergunta: unix.stackexchange.com/q/492966/85039
Sergiy Kolodyazhnyy