Guida avanzata di scripting Bash: Un'approfondita esplorazione dell'arte dello scripting di shell | ||
---|---|---|
Indietro | Avanti |
L'esecuzione di uno script di shell avvia un nuovo processo, una subshell.
Una subshell è un'istanza separata del processore dei comandi. -- la shell, quella che fornisce il prompt alla console o in una finestra xterm. Proprio come i comandi vengono interpretati al prompt della riga di comando, così fa uno script che ne elabora in modalità batch una serie. Ogni script di shell in esecuzione è, in realtà, un sottoprocesso (processo figlio) della shell genitore.
Anche uno script di shell può mettere in esecuzione dei sottoprocessi. Queste subshell consentono allo script l'elaborazione in parallelo, vale a dire, l'esecuzione simultanea di più compiti di livello inferiore.
#!/bin/bash # subshell-test.sh ( # Dentro le parentesi, quindi una subshell . . . while [ 1 ] # Ciclo infinito. do echo "Subshell in esecuzione . . ." done ) # Lo script continuerà a funzionare, #+ almeno finché non verrà terminato con un Ctl-C. exit $? # Fine dello script (ma non si arriverà mai a questo punto). Ora eseguiamo lo script: sh subshell-test.sh E, mentre lo script è in esecuzione, da un diverso xterm: ps -ef | grep subshell-test.sh UID PID PPID C STIME TTY TIME CMD 500 2698 2502 0 14:26 pts/4 00:00:00 sh subshell-test.sh 500 2699 2698 21 14:26 pts/4 00:00:24 sh subshell-test.sh ^^^^ Analisi: PID 2698, lo script, lancia il PID 2699, la subshell. Nota: La riga "UID ..." dovrebbe essere eliminata tramite il comando "grep". Qui viene mostrata a scopo illustrativo. |
Generalmente, un comando esterno presente in uno script genera un sottoprocesso, [1] al contrario di un builtin di Bash. È per questa ragione che i builtin vengono eseguiti più velocemente dei loro equivalenti comandi esterni.
Elenco di comandi tra parentesi
Una lista di comandi tra parentesi dà luogo ad una subshell.
Le variabili presenti in una subshell non sono visibili al di fuori del suo blocco di codice. Non sono accessibili al processo genitore, la shell che ha lanciato la subshell. Sono, a tutti gli effetti, variabili locali.
Esempio 20-1. Ambito di una variabile in una subshell
#!/bin/bash # subshell.sh echo echo "Siamo all'esterno della subshell." echo "Livello della subshell all'ESTERNO della subshell = $BASH_SUBSHELL" # Bash, versione 3, adotta la nuova variabile $BASH_SUBSHELL. echo; echo variabile_esterna=Esterna variabile_globale= # Definiamo una variabile globale in cui verrà "conservato" #+ il valore della variabile di subshell. ( echo "Siamo dentro alla subshell." echo "Livello della subshell all'INTERNO della subshell = $BASH_SUBSHELL" variabile_interna=Interna echo "All'interno della subshell, \"variabile_interna\" = $variabile_interna" echo "All'interno della subshell, \"variabile_esterna\" = $variabile_esterna" variabile_globale="$variabile_interna" # Questo ci consetirà di "esportare" #+ una variabile di subshell? ) echo; echo echo "Siamo fuori dalla subshell." echo "Livello della subshell all'ESTERNO della subshell = $BASH_SUBSHELL" echo if [ -z "$variabile_interna" ] then echo "variabile_interna non definita nel corpo principale della shell" else echo "variabile_interna definita nel corpo principale della shell" fi echo "Nel corpo principale della shell,\ \"variabile_interna\" = $variabile_interna" # $variabile_interna viene visualizzata come uno spazio (non inizializzata) #+ perché le variabili definite in una subshell sono "variabili locali". # Esiste un rimedio a ciò? echo "variabile_globale = "$variabile_globale"" # Perché non funziona? echo exit 0 # Domanda: # -------- # Una volta usciti da una subshell, #+ esiste un qualche modo per rientrare proprio nella stessa subshell #+ per accedere alle, o modificare le, variabili della stessa? |
Vedi anche Esempio 31-2.
Mentre la variabile interna $BASH_SUBSHELL indica il livello di annidamento di una subshell, la variabile $SHLVL non mostrerà nessun cambiamento all'interno della subshell.
|
I cambiamenti di directory effettuati in una subshell non si ripercuotono sulla shell genitore.
Esempio 20-2. Elenco dei profili utente
#!/bin/bash # allprofs.sh: visualizza i profili di tutti gli utenti # Script di Heiner Steven modificato dall'autore di questo documento. FILE=.bashrc # Il file contenente il profilo utente #+ nello script originale era ".profile". for home in `awk -F: '{print $6}' /etc/passwd` do [ -d "$home" ] || continue # Se non vi è la directory home, #+ va al successivo. [ -r "$home" ] || continue # Se non ha i permessi di lettura, va #+ al successivo. (cd $home; [ -e $FILE ] && less $FILE) done # Quando lo script termina, non è necessario un 'cd' alla directory #+ originaria, perché 'cd $home' è stato eseguito in una subshell. exit 0 |
Una subshell può essere usata per impostare un "ambiente dedicato" per un gruppo di comandi.
COMANDO1 COMANDO2 COMANDO3 ( IFS=: PATH=/bin unset TERMINFO set -C shift 5 COMANDO4 COMANDO5 exit 3 # Esce solo dalla subshell! ) # La shell genitore non è stata toccata ed il suo ambiente è preservato. COMANDO6 COMANDO7 |
Una sua applicazione permette di verificare se una variabile è stata definita.
if (set -u; : $variabile) 2> /dev/null then echo "La variabile è impostata." fi # La variabile potrebbe essere stata impostata nello script stesso, #+ oppure essere una variabile interna di Bash, #+ oppure trattarsi di una variabile d'ambiente (che è stata esportata). # Si sarebbe anche potuto scrivere # [[ ${variabile-x} != x || ${variabile-y} !=y ]] # oppure [[ ${variabile-x} != x$variabile ]] # oppure [[ ${variabile+x} = x ]] # oppure [[ ${variabile-x} != x ]] |
Un'altra applicazione è quella di verificare la presenza di un file lock:
if (set -C; : > file_lock) 2> /dev/null then : # il file_lock non esiste: nessun utente sta eseguendo lo script else echo "C'è già un altro utente che sta eseguendo quello script." exit 65 fi # Frammento di codice di Stéphane Chazelas, #+ con modifiche effettuate da Paulo Marcel Coelho Aragao. |
+
È possibile eseguire processi in parallelo per mezzo di differenti subshell. Questo permette di suddividere un compito complesso in sottocomponenti elaborate contemporaneamente.
Esempio 20-3. Eseguire processi paralleli tramite le subshell
(cat lista1 lista2 lista3 | sort | uniq > lista123) & (cat lista4 lista5 lista6 | sort | uniq > lista456) & # Unisce ed ordina entrambe le serie di liste simultaneamente. # L'esecuzione in background assicura l'esecuzione parallela. # # Stesso effetto di # cat lista1 lista2 lista3 | sort | uniq > lista123 & # cat lista4 lista5 lista6 | sort | uniq > lista456 & wait # Il comando successivo non viene eseguito finché le subshell #+ non sono terminate. diff lista123 lista456 |
Per la redirezione I/O a una subshell si utilizza l'operatore di pipe "|", come in ls -al | (comando).
Un elenco di comandi tra parentesi graffe non esegue una subshell. { comando1; comando2; comando3; . . . comandoN; } |
[1] | Un comando esterno invocato con exec (di solito) non genera un/a sottoprocesso / subshell. |