O shebang determina o shell que executa o script?

84

Essa pode ser uma pergunta boba, mas eu ainda pergunto. Se eu declarei um shebang

#!/bin/bash 

no começo de my_shell_script.sh, então eu sempre tenho que invocar esse script usando bash

[my@comp]$bash my_shell_script.sh

ou posso usar, por exemplo

[my@comp]$sh my_shell_script.sh

e meu script determina o shell em execução usando o shebang? É o mesmo com kshshell? Estou usando o AIX.

jrara
fonte
6
existe um pouco de confusão de sua parte: quando você faz "_some_shell some_script", ele inicia _some_shell e pede para interpretar some_script. Portanto, não, se você fizer "sh my_shell_script.sh", ele não interpretará o shebang, mas interpretará o script em sh. Para usar o shebang: chmod +x my_shell_script.sh ; /path/to/my_shell_script.sh # or ./my_shell_script.sh if you happen to be in its directory
Olivier Dulac

Respostas:

117

O shebang #! é uma instância legível por humanos de um número mágico que consiste na sequência de bytes 0x23 0x21, usada pela exec()família de funções para determinar se o arquivo a ser executado é um script ou um binário. Quando o shebang estiver presente, exec()será executado o executável especificado após o shebang.

Observe que isso significa que, se você chamar um script especificando o intérprete na linha de comando, como é feito nos dois casos apresentados na pergunta, exec()executará o intérprete especificado na linha de comando, ele nem olhará para o script.

Portanto, como outros observaram, se você deseja exec()chamar o intérprete especificado na linha shebang, o script deve ter o bit executável definido e invocado como ./my_shell_script.sh.

O comportamento é fácil de demonstrar com o seguinte script:

#!/bin/ksh
readlink /proc/$$/exe

Explicação:

  • #!/bin/kshdefine kshpara ser o intérprete.

  • $$ mantém o PID do processo atual.

  • /proc/pid/exe é um link simbólico para o executável do processo (pelo menos no Linux; no AIX, /proc/$$/object/a.out é um link para o executável).

  • readlink produzirá o valor do link simbólico.

Exemplo:

Nota : Estou demonstrando isso no Ubuntu, onde o shell padrão /bin/shé um link simbólico para traçar, ou seja, /bin/dashe /bin/kshé um link simbólico para o /etc/alternatives/kshqual, por sua vez, é um link simbólico para /bin/pdksh.

$ chmod +x getshell.sh
$ ./getshell.sh 
/bin/pdksh
$ bash getshell.sh 
/bin/bash
$ sh getshell.sh 
/bin/dash
Thomas Nyman
fonte
Obrigado Thomas por esta resposta. Finja que lançamos o script como um processo filho do Node.js ou Java ou qualquer outra coisa. Podemos iniciar um processo "exec" e, em seguida, o exec executará o script de shell? Peço beause Estou à procura de respostas para essa pergunta: stackoverflow.com/questions/41067872/...
Alexander Mills
1
@AlexanderMills A exec()referida nesta resposta é uma chamada do sistema, o comando execé um shell embutido, e é por isso que você não pode invocar um exec programa do Node.js. ou Java. No entanto, qualquer comando de shell invocado por, por exemplo, Runtime.exec()em Java é eventualmente processado pela exec()chamada do sistema.
Thomas Nyman
Huh, sim, eu sou indeede familiarizado com a API Java você acabou de mencionar, gostaria de saber se existe uma maneira de chamar o exec nível mais baixo () chamada de Node.js de alguma forma
Alexander Mills
@AlexanderMills eu imagino child_process.{exec(),execFile(),spawn()} tudo seria implementado usando o C exec()(através process).
Thomas Nyman
10

Sim. A propósito, não é uma pergunta boba. Uma referência para minha resposta está aqui . Iniciando um script com #!

  • É chamado de shebang ou linha "bang".

  • Não é nada além do caminho absoluto para o intérprete do Bash.

  • Consiste em um sinal numérico e um caractere de ponto de exclamação (#!), Seguido pelo caminho completo para o intérprete, como / bin / bash.

    Todos os scripts no Linux são executados usando o interpretador especificado em uma primeira linha. Quase todos os scripts bash geralmente começam com #! / Bin / bash (assumindo que o Bash tenha sido instalado no / bin) Isso garante que o Bash seja usado para interpretar o script, mesmo se for executado sob outro shell. O shebang foi introduzido por Dennis Ritchie entre a Versão 7 Unix e a 8 no Bell Laboratories. Também foi adicionado à linha BSD em Berkeley.

Ignorando uma linha de intérprete (shebang)

Se você não especificar uma linha de intérprete, o padrão geralmente é o / bin / sh. Mas, é recomendável que você defina #! / Bin / bash line.

vfbsilva
fonte
3
Para elaborar, o kernel apenas sabe como executar binários estaticamente vinculados e onde encontrar informações de intérpretes para outros (um campo especial na linha binária ou shebang). Normalmente, executar um script de shell significa seguir a linha shebang até o shell e, em seguida, seguir o campo DT_INTERP no binário do shell para o vinculador dinâmico.
Simon Richter
5
Observe também que isso não se limita aos scripts de shell. Todos os arquivos de script baseados em texto usam isso. por exemplo #!/usr/bin/perl #!/usr/local/bin/python #!/usr/local/bin/rubyOutra entrada shebang comum usado para suportar múltiplos sistemas é usar env para localizar o intérprete que deseja utilizar, como#!/usr/bin/env perl #!/usr/bin/env python
sambler
@sambler falando env, qual deve ser realmente preferido? Python e Perl costumam usar env, enquanto em scripts de shell, isso geralmente é omitido e shebang aponta para o shell em questão.
precisa saber é
1
@polemon menos dos quais é preferido e mais em quais caminhos variam. Os shells básicos estão no mesmo caminho em todos os sistemas. Versões atualizadas do perl e python podem ser instaladas em locais diferentes em sistemas diferentes, portanto, o uso do env permite que o mesmo shebang funcione sempre, e é por isso que o env é usado mais com scripts perl e python do que com scripts shell.
Sambler
envencontrar um programa em $ PATH é um pouco complicado. Ele não define variáveis ​​de ambiente como o nome indica. $ PATH pode ser um resultado diferente para diferentes usuários. Mas ajuda os scripts a serem executados sem modificação em sistemas que colocam um interpretador perl razoável em algum lugar estranho.
John Mahowald
4

A execchamada do sistema do kernel Linux entende shebangs ( #!) nativamente

Quando você faz no bash:

./something

no Linux, este chama a execchamada de sistema com o caminho ./something.

Essa linha do kernel é chamada no arquivo passado para exec: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_script.c#L25

if ((bprm->buf[0] != '#') || (bprm->buf[1] != '!'))

Ele lê os primeiros bytes do arquivo e os compara #!.

Se a comparação for verdadeira, o restante da linha será analisado pelo kernel do Linux, que fará outra execchamada com o caminho /usr/bin/env pythone o arquivo atual como o primeiro argumento:

/usr/bin/env python /path/to/script.py

e isso funciona para qualquer linguagem de script que use #como caractere de comentário.

E sim, você pode fazer um loop infinito com:

printf '#!/a\n' | sudo tee /a
sudo chmod +x /a
/a

O Bash reconhece o erro:

-bash: /a: /a: bad interpreter: Too many levels of symbolic links

#! por acaso é legível por humanos, mas isso não é necessário.

Se o arquivo execfosse iniciado com bytes diferentes, a chamada do sistema usaria um manipulador diferente. O outro manipulador interno mais importante é para arquivos executáveis ​​do ELF: https://github.com/torvalds/linux/blob/v4.8/fs/binfmt_elf.c#L1305, que verifica se há bytes 7f 45 4c 46(que também são humanos legível para .ELF). Vamos confirmar que, lendo os 4 primeiros bytes de /bin/ls, que é um executável ELF:

head -c 4 "$(which ls)" | hd 

resultado:

00000000  7f 45 4c 46                                       |.ELF|
00000004                                                                 

Portanto, quando o kernel vê esses bytes, ele pega o arquivo ELF, coloca-o na memória corretamente e inicia um novo processo com ele. Consulte também: https://stackoverflow.com/questions/8352535/how-does-kernel-get-an-executable-binary-file-running-under-linux/31394861#31394861

Por fim, você pode adicionar seus próprios manipuladores shebang com o binfmt_miscmecanismo Por exemplo, você pode adicionar um manipulador personalizado para .jararquivos . Esse mecanismo ainda suporta manipuladores por extensão de arquivo. Outro aplicativo é executar de forma transparente executáveis ​​de uma arquitetura diferente com o QEMU .

Eu não acho que o POSIX especifique shebangs, no entanto: https://unix.stackexchange.com/a/346214/32558 , embora mencione isso nas seções de justificativa e na forma "se scripts executáveis ​​são suportados pelo sistema, algo pode acontecer".

Ciro Santilli adicionou uma nova foto
fonte
1
A execução ./somethingde um shell não passará o caminho completo para exec, mas exatamente o caminho digitado. Você pode corrigir isso na sua resposta? Faça echo "$0"no seu script e você verá que é esse o caso.
AndiDog
2

De fato, se você considerar isso consequentemente, o executável observado na linha shebang é apenas um executável. Faz sentido usar algum interpretador de texto como executável, mas não é necessário. Apenas para esclarecimento e demonstração, fiz um teste bastante inútil:

#!/bin/cat
useless text
more useless text
still more useless text

Nomeado o test.txt arquivo e definir o bit exectuable chmod u+x test.txt, em seguida, "chamado" que: ./test.txt. Como esperado, o conteúdo do arquivo é produzido. Nesse caso, o gato não ignora a linha shebang. Simplesmente gera todas as linhas. Qualquer intérprete útil deve, portanto, ser capaz de ignorar esta linha de conversa. Para bash, perl e PHP, é simplesmente uma linha de comentário. Então, sim, eles ignoram a linha shebang.

Siegfried
fonte
-1

Pelo que pude reunir, sempre que um arquivo tem um conjunto de bits executável e é chamado, o kernel analisa o cabeçalho do arquivo para determinar como proceder (tanto quanto eu sei, você pode adicionar manipuladores personalizados para formatos de arquivo personalizados via LKMs). Se o arquivo parecer ser um arquivo de texto com um #! combinação no início, sua execução é despachada para outro executável (geralmente um tipo de shell), um caminho para o qual deve ser especificado diretamente após o referido shebang, na mesma linha. O kernel prossegue para executar o shell e passar o arquivo para ele manipular.

Resumindo, não importa com qual shell você invoca o script - o kernel enviará a execução para a apropriada de qualquer maneira.

Vladimir Parka
fonte
4
Há uma diferença marcante entre bash ./myscript.she ./myscript.sh.
um CVn
O que você quer dizer com essa "diferença marcante"?
Jrara #
3
@jrara Veja minha resposta, a afirmação de que "não importa em qual shell você invoca o script" simplesmente não é verdadeira.
Thomas Nyman