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
execvefailed, 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.