A resposta definitiva para "como os programas são executados" no Linux é o par de artigos sobre LWN.net intitulado, surpreendentemente, Como os programas são executados e Como os programas são executados: ELF binários . O primeiro artigo aborda scripts brevemente. (Estritamente falando, a resposta definitiva está no código-fonte, mas esses artigos são mais fáceis de ler e fornecem links para o código-fonte.)
Uma pequena experimentação mostra que você praticamente acertou, e que a execução de um arquivo contendo uma lista simples de comandos, sem um shebang, precisa ser manipulada pelo shell. A página de manual execve (2) contém o código-fonte de um programa de teste, execve; vamos usar isso para ver o que acontece sem um shell. Primeiro, escreva um testcript, testscr1
, contendo
#!/bin/sh
pstree
e outro, testscr2
, contendo apenas
pstree
Torne-os executáveis e verifique se ambos são executados a partir de um shell:
chmod u+x testscr[12]
./testscr1 | less
./testscr2 | less
Agora tente novamente, usando execve
(supondo que você o construiu no diretório atual):
./execve ./testscr1
./execve ./testscr2
testscr1
ainda é executado, mas testscr2
produz
execve: Exec format error
Isso mostra que o shell manipula testscr2
de maneira diferente. Ele não processa o script em si, mas ainda usa /bin/sh
para fazer isso; isso pode ser verificado pela canalização de testscr2
para less
:
./testscr2 | less -ppstree
No meu sistema, recebo
|-gnome-terminal--+-4*[zsh]
| |-zsh-+-less
| | '-sh---pstree
Como você pode ver, há o shell que eu estava usando, zsh
, que iniciou less
e um segundo shell, sh
( dash
no meu sistema), para executar o script, que foi executado %código%. Em pstree
, isso é tratado por zsh
em zexecve
: o shell usa Src/exec.c
para tentar executar o comando, e se isso falhar, ele lê o arquivo para ver se ele tem um shebang, processando-o de acordo (o que o kernel também terá feito), e se falha ele tenta executar o arquivo com execve(2)
, contanto que ele não tenha lido nenhum byte zero do arquivo:
for (t0 = 0; t0 != ct; t0++)
if (!execvebuf[t0])
break;
if (t0 == ct) {
argv[-1] = "sh";
winch_unblock();
execve("/bin/sh", argv - 1, newenvp);
}
sh
tem o mesmo comportamento, implementado em bash
com um comentário útil (como apontado por taliezin ):
Execute a simple command that is hopefully defined in a disk file somewhere.
fork ()
- connect pipes
- look up the command
- do redirections
execve ()
- If the
execve
failed, see if the file has executable mode set. If so, and it isn't a directory, then execute its contents as a shell script.
O POSIX define um conjunto de funções, conhecidas como as funções execute_cmd.c
, que envolvem exec(3)
e forneça essa funcionalidade também; veja resposta do muru para detalhes. No Linux, pelo menos, essas funções são implementadas pela biblioteca C, não pelo kernel.