Bash sta ricaricando automaticamente (iniettando) gli aggiornamenti in uno script in esecuzione dopo averlo salvato: Perché? Qualche uso pratico?


10

Stavo scrivendo uno script bash e mi è capitato di aggiornare il codice (salvato il file dello script su disco) mentre lo script era in attesa di alcuni input in un whileciclo. Dopo essere tornato al terminale e aver continuato con la precedente chiamata dello script, bash ha dato un errore sulla sintassi del file:

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

Quindi ho provato a fare quanto segue:

1 °: crea uno script, self-update.shchiamiamolo:

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

Quello che fa lo script è leggere il suo codice, cambiare la parola "PRIMA" in "DOPO", quindi riscriverlo con il nuovo codice.

2 Eseguilo:

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

3a meraviglia ...

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

Ora, non avrei immaginato che sulla stessa invocazione avrebbe prodotto DOPO! , nella seconda manche di sicuro, ma non nella prima.

Quindi la mia domanda è: è intenzionale (in base alla progettazione)? o è a causa del modo in cui bash esegue lo script? Riga per riga o comando per comando. C'è un buon uso di tale comportamento? Qualche esempio di esso?


Modifica: ho provato a riformattare il file per mettere tutti i comandi in una riga, non funziona ora:

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

Produzione:

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

Mentre si sposta la echostringa sulla riga successiva, separandola dalla cpchiamata rewriting ( ):

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

E ora funziona di nuovo:

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


1
Ho guardato attraverso la fonte, ma non riesco davvero a trovare alcuna spiegazione per questo. Sono abbastanza sicuro però di aver visto alcuni programmi di installazione autoestraenti che sono stati implementati come script di shell alcuni anni fa. Questi script contengono alcune righe di comandi eseguibili della shell e quindi un enorme blocco di dati che viene letto da questi comandi. Quindi suppongo che questo sia progettato in questo modo in modo che bash non debba leggere l'intero script in memoria che non funzionerebbe con enormi script autoestraenti.
Martin von Wittich,

Ecco un esempio di uno script SFX: ftp.games.skynet.be/pub/wolfenstein/…
Martin von Wittich,

Bello! Ricordo di aver visto questi script autoinstallanti, il driver AMD Catalyst (proprietario) viene ancora spedito in questo modo, si autoestraisce e quindi si installa. Questi dipendono da tale comportamento di lettura dei file in blocchi. Grazie per l'esempio!
aularon,

Risposte:


12

Questo è di progettazione. Bash legge gli script in blocchi. Quindi leggerà una parte dello script, eseguirà tutte le righe che può e quindi leggerà il pezzo successivo.

Quindi ti imbatti in qualcosa del genere:

  • Bash legge i primi 256 byte (byte 0-255) dello script.
  • All'interno di quei primi 256 byte c'è un comando che richiede un po 'di tempo per l'esecuzione e bash avvia quel comando, in attesa che esca.
  • Mentre il comando è in esecuzione, lo script viene aggiornato e la parte modificata è dopo i 256 byte già letti.
  • Al termine dell'esecuzione del comando bash, continua a leggere il file, riprendendo da dove si trovava, ottenendo byte 256-511.
  • Quella parte della sceneggiatura è cambiata, ma bash non lo sa.

Dove questo diventa ancora più problematico è che se si modifica qualcosa prima del byte 256. Supponiamo di eliminare un paio di righe. Quindi i dati nello script che erano al byte 256, ora sono da qualche altra parte, diciamo al byte 156 (100 byte prima). Per questo motivo, quando bash continua a leggere, otterrà ciò che originariamente era 356.

Questo è solo un esempio. Bash non legge necessariamente 256 byte alla volta. Non so esattamente quanto legge alla volta, ma non importa, il comportamento è sempre lo stesso.


No, viene letto da blocchi, ma si riavvolge dove doveva essere sicuro di leggere il comando successivo così com'è dopo il ritorno del comando precedente.
Stéphane Chazelas,

@StephaneChazelas Da dove lo prendi? Ho appena fatto una battuta e non è nemmeno così come statil file per vedere se è cambiato. Nessuna lseekchiamata.
Patrick,

Vedere pastie.org/8662761 per le parti rilevanti dell'output di strace. Guarda come è echo foocambiato in a echo bardurante sleep. Si è comportato così fin dalle versioni 2, quindi non credo sia un problema di versione.
Stéphane Chazelas,

Apparentemente legge il file riga per riga, ho provato con il file e quello che ho scoperto. Modificherò la mia domanda per evidenziare quel comportamento.
aularon,

@Patrick se riesci ad aggiornare la tua risposta per riflettere che sta leggendo riga per riga, quindi posso accettare la tua risposta. (Controlla la mia modifica alla domanda sull'argomento).
aularon,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.