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

10

Eu estava escrevendo um script bash e atualizei o código (salvou o arquivo de script em disco) enquanto o script aguardava alguma entrada em um whileloop. Depois que voltei ao 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.shvamos 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, alterar a palavra 'ANTES' para 'APÓS' e depois 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 imaginado que, na mesma invocação, sairia DEPOIS! , na segunda execução, com certeza, mas não na primeira.

Então, minha pergunta é: é intencional (por design)? ou é por causa da maneira como o bash executa o script? Linha por linha ou comando por comando. Existe algum bom uso desse comportamento? Algum exemplo disso?


Edit: Eu tentei reformatar o arquivo para colocar todos os comandos em uma linha, ele não funciona agora:

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

Resultado:

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

Ao mover a echostring para a próxima linha, separe-a da cpchamada rewriting ( ):

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

E agora funciona novamente:

aularon@aularon-laptop:~$ ./self-update.sh 
String: AFTER!.
aularon
fonte
1
Veja também: stackoverflow.com/questions/4754592/…
Martin von Wittich
1
Eu procurei na fonte, mas não consigo encontrar nenhuma explicação para isso. Tenho quase certeza de que já vi alguns instaladores de extração automática que foram implementados como scripts de shell alguns anos atrás. Esses scripts contêm algumas linhas de comandos executáveis ​​do shell e, em seguida, um enorme bloco de dados que é lido por esses comandos. Então, assumirei que isso foi projetado dessa maneira, para que o bash não precise ler o script inteiro na memória, o que não funcionaria com grandes scripts de extração automática.
Martin von Wittich
Aqui está um exemplo de script SFX: ftp.games.skynet.be/pub/wolfenstein/…
Martin von Wittich
Agradável! Lembro-me de ter visto esses scripts de instalação automática, o driver AMD Catalyst (proprietário) ainda é enviado assim, é extraído automaticamente e depois instalado. Isso depende desse comportamento de ler os arquivos em pedaços. Obrigado pelo exemplo!
aularon 23/01

Respostas:

12

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

Então você encontra algo assim:

  • O Bash lê os primeiros 256 bytes (bytes 0-255) do script.
  • Dentro desses primeiros 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 é posterior aos 256 bytes que já foram lidos.
  • Quando o comando bash estava em execução, ele continua lendo o arquivo, retomando de onde estava, obtendo os bytes 256-511.
  • Essa parte do script mudou, mas o bash não sabe disso.

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

Este é apenas um exemplo. O Bash não lê necessariamente 256 bytes de cada vez. Não sei exatamente quanto ele lê ao mesmo tempo, mas não importa, o comportamento ainda é o mesmo.

Patrick
fonte
Não, ele lê por partes, mas retrocede para onde deveria ter certeza de ler o próximo comando, pois está após o retorno do comando anterior.
Stéphane Chazelas
@StephaneChazelas De onde você está tirando isso? Acabei de fazer um rastro e nem sequer stato arquivo para ver se ele foi alterado. Sem lseekchamadas.
Patrick
Veja pastie.org/8662761 para as partes relevantes da saída strace. Veja como o echo foomudou para a echo bardurante o sleep. Ele está se comportando assim desde as versões 2, então não acho que seja um problema de versão.
Stéphane Chazelas
Aparentemente, ele lê o arquivo linha por linha, tentei com o arquivo e foi o que descobri. Vou editar minha pergunta para destacar esse comportamento.
aularon
@ Patrick, se você puder atualizar sua resposta para refletir que está lendo linha por linha, para que eu possa aceitar sua resposta. (Verifique minha edição da pergunta sobre esse assunto).
aularon 24/01