Por que o "ps ax" não encontra um script bash em execução sem o cabeçalho "#!"?

13

Quando executo este script, pretendo ser executado até ser morto ...

# foo.sh

while true; do sleep 1; done

... Não consigo encontrá-lo usando ps ax :

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21110 pts/3    S+     0:00 grep --color=auto foo.sh

... mas se eu adicionar o cabeçalho " #! " comum ao script ...

#! /usr/bin/bash
# foo.sh

while true; do sleep 1; done

... então o script se torna localizável pelo mesmo comando ps ...

>./foo.sh

// In a separate shell:
>ps ax | grep foo.sh
21319 pts/43   S+     0:00 /usr/bin/bash ./foo.sh
21324 pts/3    S+     0:00 grep --color=auto foo.sh

Por que isso é assim?
Esta pode ser uma questão relacionada: Eu pensei que " # " era apenas um prefixo de comentário, e se sim " #! /usr/bin/bash " é nada mais do que um comentário. Mas " #! " carrega algum significado maior do que apenas um comentário?

    
por StoneThrow 08.06.2018 / 20:20

3 respostas

13

Quando o shell interativo atual for bash e você executar um script sem #! -line, o bash executará o script. O processo será exibido na saída ps ax como apenas bash .

$ cat foo.sh
# foo.sh

echo "$BASHPID"
while true; do sleep 1; done

$ ./foo.sh
55411

Em outro terminal:

$ ps -p 55411
  PID TT  STAT       TIME COMMAND
55411 p2  SN+     0:00.07 bash

Relacionados:

As seções relevantes formam o manual bash :

If this execution fails because the file is not in executable format, and the file is not a directory, it is assumed to be a shell script, a file containing shell commands. A subshell is spawned to execute it. This subshell reinitializes itself, so that the effect is as if a new shell had been invoked to handle the script, with the exception that the locations of commands remembered by the parent (see hash below under SHELL BUILTIN COMMANDS) are retained by the child.

If the program is a file beginning with #!, the remainder of the first line specifies an interpreter for the program. The shell executes the specified interpreter on operating systems that do not handle this executable format themselves. [...]

Isso significa que executar ./foo.sh na linha de comando, quando foo.sh não possui #! -line, é o mesmo que executar os comandos no arquivo em uma subshell, ou seja,

$ ( echo "$BASHPID"; while true; do sleep 1; done )

Com uma #! -line adequada apontando para p. /bin/bash , é como fazer

$ /bin/bash foo.sh
    
por 08.06.2018 / 20:29
12

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
    
por 08.06.2018 / 20:48
-1

No primeiro caso, o script é executado por um filho bifurcado do seu shell atual.

Você deve primeiro executar echo $$ e, em seguida, dar uma olhada em um shell que possui o id do processo do seu shell como id do processo pai.

    
por 08.06.2018 / 20:28