In che modo Linux gestisce gli script di shell?


22

Per questa domanda, consideriamo uno script di shell bash, sebbene questa domanda debba essere applicabile a tutti i tipi di script di shell.

Quando qualcuno esegue uno script di shell, Linux carica tutti gli script contemporaneamente (forse in memoria) o legge i comandi di script uno per uno (riga per riga)?

In altre parole, se eseguo uno script di shell e lo cancello prima che l'esecuzione venga completata, l'esecuzione verrà terminata o continuerà così com'è?


3
Provalo. (Continuerà.)
Devnull

1
@devnull c'è in realtà una domanda interessante qui. Certo, se continuerà o meno è banale testare, ma ci sono differenze tra file binari (che vengono caricati in memoria) e script con una riga shebang o script senza una riga shebang.
terdon

1
Potresti essere interessato a questa risposta
terdon

23
Ai fini del tuo intento reale, di eliminare lo script della shell durante la sua esecuzione, non importa se viene letto tutto in una volta o riga per riga. In Unix, un inode non viene effettivamente cancellato (anche se non ci sono collegamenti ad esso da alcuna directory) fino alla chiusura dell'ultimo file aperto. In altre parole, anche se la shell legge nello script della shell riga per riga durante l'esecuzione, è comunque sicuro eliminarla. L'unica eccezione è se la tua shell è il tipo che chiude e riapre lo script della shell ogni volta, ma se lo fa, hai problemi molto più grandi (di sicurezza).
Chris Jester-Young,

Risposte:


33

Se usi strace puoi vedere come viene eseguito uno script di shell quando viene eseguito.

Esempio

Di 'che ho questo script di shell.

$ cat hello_ul.bash 
#!/bin/bash

echo "Hello Unix & Linux!"

Eseguendolo usando strace:

$ strace -s 2000 -o strace.log ./hello_ul.bash
Hello Unix & Linux!
$

Dare un'occhiata all'interno del strace.logfile rivela quanto segue.

...
open("./hello_ul.bash", O_RDONLY)       = 3
ioctl(3, SNDCTL_TMR_TIMEBASE or SNDRV_TIMER_IOCTL_NEXT_DEVICE or TCGETS, 0x7fff0b6e3330) = -1 ENOTTY (Inappropriate ioctl for device)
lseek(3, 0, SEEK_CUR)                   = 0
read(3, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 80) = 40
lseek(3, 0, SEEK_SET)                   = 0
getrlimit(RLIMIT_NOFILE, {rlim_cur=1024, rlim_max=4*1024}) = 0
fcntl(255, F_GETFD)                     = -1 EBADF (Bad file descriptor)
dup2(3, 255)                            = 255
close(3)     
...

Una volta letto il file, viene quindi eseguito:

...
read(255, "#!/bin/bash\n\necho \"Hello Unix & Linux!\"\n", 40) = 40
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fc0b38ba000
write(1, "Hello Unix & Linux!\n", 20)   = 20
rt_sigprocmask(SIG_BLOCK, NULL, [], 8)  = 0
read(255, "", 40)                       = 0
exit_group(0)                           = ?

In quanto sopra possiamo vedere chiaramente che l'intero script sembra essere letto come una singola entità, e quindi eseguito lì dopo. Quindi "apparirebbe" almeno nel caso di Bash in cui legge il file e quindi lo esegue. Quindi pensi di poter modificare lo script mentre è in esecuzione?

NOTA: non farlo! Continua a leggere per capire perché non dovresti scherzare con un file di script in esecuzione.

E gli altri interpreti?

Ma la tua domanda è leggermente off. Non è Linux che sta necessariamente caricando il contenuto del file, è l'interprete che sta caricando il contenuto, quindi dipende davvero da come è stato implementato l'interprete se carica il file interamente o in blocchi o righe alla volta.

Quindi perché non possiamo modificare il file?

Se usi uno script molto più grande, noterai che il test sopra è un po 'fuorviante. In effetti la maggior parte degli interpreti carica i propri file in blocchi. Questo è piuttosto standard con molti strumenti Unix in cui caricano blocchi di un file, lo elaborano e quindi caricano un altro blocco. Puoi vedere questo comportamento con queste domande e risposte che ho scritto qualche tempo fa riguardo al greptitolo: Quanto testo consuma ogni volta grep / egrep?.

Esempio

Supponiamo di creare il seguente script di shell.

$ ( 
    echo '#!/bin/bash'; 
    for i in {1..100000}; do printf "%s\n" "echo \"$i\""; done 
  ) > ascript.bash;
$ chmod +x ascript.bash

Risultato in questo file:

$ ll ascript.bash 
-rwxrwxr-x. 1 saml saml 1288907 Mar 23 18:59 ascript.bash

Che contiene il seguente tipo di contenuto:

$ head -3 ascript.bash ; echo "..."; tail -3 ascript.bash 
#!/bin/bash
echo "1"
echo "2"
...
echo "99998"
echo "99999"
echo "100000"

Ora quando esegui questo usando la stessa tecnica sopra con strace:

$ strace -s 2000 -o strace_ascript.log ./ascript.bash
...    
read(255, "#!/bin/bash\necho \"1\"\necho \"2\"\necho \"3\"\necho \"4\"\necho \"5\"\necho \"6\"\necho \"7\"\necho \"8\"\necho \"9\"\necho \"10\"\necho 
...
...
\"181\"\necho \"182\"\necho \"183\"\necho \"184\"\necho \"185\"\necho \"186\"\necho \"187\"\necho \"188\"\necho \"189\"\necho \"190\"\necho \""..., 8192) = 8192

Noterai che il file viene letto con incrementi di 8 KB, quindi probabilmente Bash e altre shell non caricheranno un file nella sua interezza, ma li leggeranno in blocchi.

Riferimenti


@terdon - sì, ricordo di aver visto prima le domande e le risposte.
slm

5
Con uno script da 40 byte, sicuramente, viene letto in un blocco. Prova con uno script> 8kB.
Gilles 'SO-smetti di essere malvagio' il

Non ho mai provato, ma penso che la rimozione dei file non sia stata effettivamente eseguita fino a quando tutti i processi non chiudono il descrittore di file associato al file rimosso, quindi bash potrebbe continuare a leggere dal file rimosso.
Farid Nouri Neshat,

@Gilles - sì, ho aggiunto un esempio, ci stavo arrivando.
slm

2
Questo comportamento dipende dalla versione. Ho provato con la versione 3.2.51 (1) di bash e ho scoperto che non ha superato la riga corrente (vedi questa risposta dello stackoverflow ).
Gordon Davisson,

11

Questo dipende più dalla shell che dal sistema operativo.

A seconda della versione, kshleggere lo script su richiesta con un blocco di 8k o 64k byte.

bashleggi lo script riga per riga. Tuttavia, dato che le righe dei fatti possono avere una lunghezza arbitraria, legge ogni volta 8176 byte dall'inizio della riga successiva da analizzare.

Questo è per costruzioni semplici, cioè una serie di comandi semplici.

Se vengono utilizzati comandi strutturati della shell (nel caso in cui la risposta accettata non venga presa in considerazione ) come un for/do/doneloop, uno case/esacswitch, un documento here, una subshell racchiusa tra parentesi, una definizione di funzione, ecc. E qualsiasi combinazione di quanto sopra, gli interpreti della shell leggono alla fine della costruzione per assicurarsi innanzitutto che non vi siano errori di sintassi.

Questo è in qualche modo inefficiente in quanto lo stesso codice può essere letto più volte, ma mitigato dal fatto che questo contenuto viene normalmente memorizzato nella cache.

Qualunque sia l'interprete della shell, è molto saggio modificare uno script della shell mentre viene eseguito poiché la shell è libera di rileggere qualsiasi porzione dello script e questo può portare a errori di sintassi imprevisti se non sincronizzati.

Nota anche che bash potrebbe arrestarsi in modo anomalo con una violazione della segmentazione quando non è in grado di memorizzare una costruzione di script eccessivamente grande che ksh93 può leggere in modo impeccabile.


7

Dipende da come funziona l'interprete che esegue lo script. Tutto ciò che il kernel fa è notare che il file da eseguire inizia #!, essenzialmente esegue il resto della riga come programma e gli fornisce l'eseguibile come argomento. Se l'interprete elencato lì legge quel file riga per riga (come fanno le shell interattive con ciò che si digita), questo è ciò che si ottiene (ma le strutture del loop multilinea vengono lette e mantenute in giro per la ripetizione); se l'interprete assorbe il file in memoria, lo elabora (forse lo compila in una rappresentazione intermedia, come fanno Perl e Pyton) il file viene letto per intero prima di essere eseguito.

Se nel frattempo si elimina il file, il file non viene eliminato fino a quando l'interprete non lo chiude (come sempre, i file scompaiono quando l'ultimo riferimento, sia esso una voce di directory o un processo che lo tiene aperto) scompare.


4

Il file 'x':

cat<<'dog' >xyzzy
LANG=C
T=`tty`
( sleep 2 ; ls -l xyzzy >$T ) &
( sleep 4 ; rm -v xyzzy >$T ) &
( sleep 4 ; ls -l xyzzy >$T ) &
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
echo alive. ; sleep 1
dog

sh xyzzy

La corsa:

~/wrk/tmp$ sh x
alive.
alive.
alive.
-rw-r--r-- 1 yeti yeti 287 Mar 23 16:57 xyzzy
alive.
removed `xyzzy'
ls: cannot access xyzzy: No such file or directory
alive.
alive.
alive.
alive.
~/wrk/tmp$ _

IIRC un file non viene cancellato fintanto che un processo lo mantiene aperto. La cancellazione rimuove solo il DIRENT specificato.

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.