Quando um script de shell começa com #!
, essa primeira linha é um comentário no que diz respeito ao shell. No entanto, os dois primeiros caracteres são significativos para outra parte do sistema: o kernel. Os dois caracteres #!
são chamados de shebang . Para entender o papel da shebang, você precisa entender como um programa é executado.
Executar um programa a partir de um arquivo requer ação do kernel. Isso é feito como parte da execve
chamada do sistema. O kernel precisa verificar as permissões do arquivo, liberar os recursos (memória, etc.) associados ao arquivo executável atualmente em execução no processo de chamada, alocar recursos para o novo arquivo executável e transferir o controle para o novo programa (e mais coisas que Eu não vou mencionar). A chamada do sistema execve
substitui o código do processo atualmente em execução; há uma chamada de sistema separada fork
para criar um novo processo.
Para fazer isso, o kernel deve suportar o formato do arquivo executável. Este arquivo deve conter código de máquina, organizado de forma que o kernel entenda. Um script de shell não contém código de máquina, portanto, não pode ser executado dessa maneira.
O mecanismo shebang permite que o kernel adie a tarefa de interpretar o código para outro programa. Quando o kernel vê que o arquivo executável começa com #!
, ele lê os próximos caracteres e interpreta a primeira linha do arquivo (menos o espaço #!
e opcional) como um caminho para outro arquivo (mais argumentos, que Eu não vou discutir aqui). Quando o kernel é instruído a executar o arquivo /my/script
, e ele vê que o arquivo começa com a linha #!/some/interpreter
, o kernel executa /some/interpreter
com o argumento /my/script
. Em seguida, cabe a /some/interpreter
decidir que /my/script
é um arquivo de script que deve ser executado.
E se um arquivo não contiver código nativo em um formato que o kernel entenda e não inicie com um shebang? Bem, o arquivo não é executável e a chamada do sistema execve
falha com o código de erro ENOEXEC
(erro de formato executável).
Este pode ser o fim da história, mas a maioria das shells implementam um recurso de fallback. Se o kernel retornar ENOEXEC
, o shell examinará o conteúdo do arquivo e verificará se ele se parece com um script de shell. Se o shell achar que o arquivo se parece com um shell script, ele o executa sozinho. Os detalhes de como isso faz isso depende do shell. Você pode ver um pouco do que está acontecendo, adicionando ps $$
em seu script e mais observando o processo com strace -p1234 -f -eprocess
, em que 1234 é o PID do shell.
No bash, esse mecanismo de fallback é implementado chamando fork
, mas não execve
. O processo bash filho limpa seu estado interno sozinho e abre o novo arquivo de script para executá-lo. Portanto, o processo que executa o script ainda está usando a imagem de código bash original e os argumentos da linha de comando original transmitidos quando você chamou bash originalmente. ATT ksh se comporta da mesma maneira.
% bash --norc
bash-4.3$ ./foo.sh
PID TTY STAT TIME COMMAND
21913 pts/2 S+ 0:00 bash --norc
Dash, em contraste, reage a ENOEXEC
chamando /bin/sh
com o caminho para o script passado como argumento. Em outras palavras, quando você executa um script shebangless de dash, ele se comporta como se o script tivesse uma linha shebang com #!/bin/sh
. Mksh e zsh se comportam da mesma maneira.
% dash
$ ./foo.sh
PID TTY STAT TIME COMMAND
21427 pts/2 S+ 0:00 /bin/sh ./foo.sh