O Bash está automaticamente recarregando (injetando) atualizações em um script em execução ao salvá-lo: Por quê? Qualquer uso prático?

7

Eu estava escrevendo um script bash e atualizei o código (salvei o arquivo de script em disco) enquanto o script aguardava alguma entrada em um loop while . Depois que voltei para o terminal e continuei com a invocação anterior do script, o bash deu um erro sobre a sintaxe do arquivo:

/home/aularon/bin/script: line 58: unexpected EOF while looking for matching '"'
/home/aularon/bin/script: line 67: syntax error: unexpected end of file

Então, tentei fazer o seguinte:

1º: crie um script, self-update.sh vamos chamá-lo:

#!/bin/bash
fname=$(mktemp)
cat $0 | sed 's/BEFORE\./AFTER!./' > $fname
cp $fname $0
rm -f $fname
echo 'String: BEFORE.';

O que o script faz é ler seu código, alterando a palavra 'BEFORE' para 'AFTER' e, em seguida, reescrever-se com o novo código.

Execute:

chmod +x self-update.sh
./self-update.sh

maravilha ...

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.

Agora, eu não teria adivinhado que na mesma invocação ele produziria DEPOIS! , na segunda execução, com certeza, mas não na primeira.

Então, minha pergunta é: é intencional (por design)? ou é por causa do modo como o bash roda o script? Linha por linha ou comando por comando. Existe algum bom uso de tal comportamento? Algum exemplo disso?

Editar: Eu tentei reformatar o arquivo para colocar todo o comando em uma linha, isso não funciona agora:

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;echo 'String: BEFORE.';

Saída:

aularon@aularon-laptop:~$ ./self-update.sh #First invocation
String: BEFORE.
aularon@aularon-laptop:~$ ./self-update.sh #Second invocation
String: AFTER!.

Enquanto estiver movendo a string echo para a próxima linha, separando-a da chamada reescrita ( cp ):

#!/bin/bash
fname=$(mktemp);cat $0 | sed 's/BEFORE\./AFTER!./' > $fname;cp $fname $0;rm -f $fname;
echo 'String: BEFORE.';

E agora funciona de novo:

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.
    
por aularon 23.01.2014 / 22:02

1 resposta

9

Isso é por design. Bash lê scripts em pedaços. Então, ele lerá uma parte do script, executará todas as linhas que puder e lerá a próxima parte.

Então você se depara com algo assim:

  • Bash lê os primeiros 256 bytes (bytes 0-255) do script.
  • Dentro desse primeiro 256 bytes há um comando que demora um pouco para ser executado, e o bash inicia esse comando, esperando que ele saia.
  • Enquanto o comando está em execução, o script é atualizado e a parte alterada é após os 256 bytes já lidos.
  • Quando o comando bash estava em execução, ele continua lendo o arquivo, retomando de onde estava, obtendo bytes de 256 a 511.
  • Essa parte do script mudou, mas o bash não sabe disso.

Quando isso se torna ainda mais problemático é que se você editar qualquer coisa antes do byte 256. Digamos que você apague algumas linhas. Em seguida, os dados no script que estava no byte 256 estão agora em algum outro lugar, digamos, no byte 156 (100 bytes anteriores). Por causa disso, quando o bash continuar lendo, ele obterá o que era originalmente 356.

Este é apenas um exemplo. Bash não necessariamente lê 256 bytes de cada vez. Eu não sei exatamente o que lê de cada vez, mas isso não importa, o comportamento ainda é o mesmo.

    
por 23.01.2014 / 22:17