Eu sempre pensei que os shells analisam scripts inteiros, construindo um AST e, em seguida, executam esse AST da memória. No entanto, acabei de ler um comentário de Stéphane Chazelas e testei a execução deste script, edit-while-executing.sh:
#!/bin/bash
echo start
sleep 10
e então enquanto ele estava dormindo correndo:
$ echo "echo end" >> edit-while-executing.sh
e funcionou para fazer com que ele imprimisse "fim" no final.
No entanto, ao tentar modificar isso:
#!/bin/bash
while true; do
echo yes
done
fazendo:
$ printf "%s" "no " | dd of=edit-while-executing.sh conv=notrunc seek=35 bs=1
Não funcionou e continuou imprimindo "sim".
Também me perguntei se outros intérpretes não shell também funcionavam assim e tentei o equivalente ao primeiro script com python, mas não funcionou. No entanto, talvez o python não seja mais um interpretador e seja mais um compilador JIT.
Então, para reiterar minha pergunta, esse é um comportamento onipresente aos shells e limitado a eles ou também presente em outros interpretadores (aqueles que não são considerados shells)? Além disso, como isso funciona de modo que eu possa fazer a primeira modificação, mas não a segunda?
Esse recurso está presente em outros intérpretes que oferecem o que é chamado de arquivo
read
eval
print
loop
. LISPéuma linguagem bastante antiga com tal recurso, e o Common LISP tem umaread
função que irá ler aqui a expressão(+ 2 2)
que pode ser passadaeval
para avaliação (embora no código real você não queira fazer desta forma por várias razões de segurança ):também podemos definir nosso próprio REPL muito simples sem muito em termos de recursos ou depuração ou praticamente qualquer outra coisa, mas isso mostra as partes do REPL:
Basicamente, como diz na placa de identificação, os dados são lidos, avaliados, impressos e, em seguida, (supondo que nada tenha travado e ainda haja eletricidade ou algo alimentando o dispositivo) ele volta para a leitura Não há necessidade de construir um AST com antecedência. (SBCL precisa de adições
force-output
efresh-line
por motivos de exibição, outras implementações de LISP comuns podem ou não.)Outras coisas com REPL incluem TCL ("um shell mordido por um LISP radioativo") que inclui coisas gráficas com Tk
Ou FORTH aqui para definir uma função
f>c
para fazer conversão de temperatura (os "ok" são adicionados porgforth
):Então, isso é executado indefinidamente em Bash/dash/ksh/zsh (ou pelo menos até que seu disco fique cheio):
A coisa a notar, é que apenas coisas
anexadasadicionadas ao arquivo de script após a última linha que o shell leu são importantes. Os shells não voltam para reler as partes anteriores, o que eles nem poderiam fazer, se a entrada fosse um pipe.A construção semelhante não funciona em Perl, ela lê o arquivo inteiro antes de ser executado.
Podemos ver que ele também faz isso quando recebe uma entrada por meio de um pipe. Isso dá um erro de sintaxe (e apenas isso) após 1 segundo:
Enquanto o mesmo script é canalizado para, por exemplo, Bash, imprime
hello
e, em seguida, lança o erro de sintaxe um segundo depois.Python parece semelhante ao Perl com entrada canalizada, mesmo que o interpretador execute um loop read-eval-print quando interativo.
Além de ler o script de entrada linha por linha, pelo menos Bash e dash processam argumentos para
eval
uma linha de cada vez:Zsh e ksh dão o erro imediatamente.
Da mesma forma para scripts de origem, desta vez o Zsh também executa linha por linha, assim como Bash e dash:
Pelo menos uma concha, peixe, não exibe esse comportamento (mas o peixe é incomum de outras maneiras):
(Uma versão anterior desta resposta tinha observações equivocadas de Python e Ruby.)