Nella terminologia POSIX, un ambiente subshell è collegato alla nozione di Shell Execution Environment .
Un ambiente subshell è un ambiente di esecuzione shell separato creato come duplicato dell'ambiente genitore. Quell'ambiente di esecuzione include cose come file aperti, umask, directory di lavoro, variabili / funzioni / alias della shell ...
Le modifiche all'ambiente subshell non influiscono sull'ambiente padre.
Tradizionalmente nella shell Bourne o ksh88 su cui si basa la specifica POSIX, ciò è stato fatto tramite il fork di un processo figlio.
Le aree in cui POSIX richiede o consente l'esecuzione di un comando in un ambiente subshell sono quelle in cui tradizionalmente ksh88 ha inoltrato un processo di shell figlio.
Non impone tuttavia implementazioni per utilizzare un processo figlio per quello.
Una shell può scegliere invece di implementare quell'ambiente di esecuzione separato nel modo che preferisce.
Ad esempio, ksh93 lo fa salvando gli attributi dell'ambiente di esecuzione principale e ripristinandoli al termine dell'ambiente subshell in contesti in cui è possibile evitare il fork (un'ottimizzazione in quanto il fork è piuttosto costoso sulla maggior parte dei sistemi).
Ad esempio, in:
cd /foo; pwd
(cd /bar; pwd)
pwd
POSIX richiede cd /foo
di essere eseguito in un ambiente separato e che per produrre qualcosa di simile:
/foo
/bar
/foo
Non richiede che venga eseguito in un processo separato. Ad esempio, se stdout diventa una pipe interrotta, l' pwd
esecuzione nell'ambiente subshell potrebbe far sì che SIGPIPE venga inviato all'unico e unico processo di shell.
La maggior parte delle shell comprese bash
lo implementeranno valutando il codice all'interno (...)
di un processo figlio (mentre il processo parent attende la sua conclusione), ma ksh93 eseguirà invece il codice all'interno (...)
, tutto nello stesso processo:
- ricorda che è in un ambiente subshell.
- su
cd
, salva la precedente directory di lavoro (in genere su un descrittore di file aperto con O_CLOEXEC), salva il valore delle variabili OLDPWD, PWD e tutto ciò che cd
potrebbe modificare e quindi fai ilchdir("/bar")
- al ritorno dalla subshell, la directory di lavoro corrente viene ripristinata (con un
fchdir()
su quel fd salvato) e tutto il resto che la subshell potrebbe aver modificato.
Ci sono contesti in cui un processo figlio non può essere evitato. ksh93 non inserisce:
var=$(subshell)
(subshell)
Ma lo fa
{ subshell; } &
{ subshell; } | other command
Cioè, i casi in cui le cose devono funzionare in processi separati in modo che possano essere eseguite contemporaneamente.
Le ottimizzazioni di ksh93 vanno oltre. Ad esempio, mentre sei dentro
var=$(pwd)
la maggior parte delle shell esegue il fork di un processo, fa in modo che il figlio esegua il pwd
comando con lo stdout reindirizzato su una pipe, pwd
scriva la directory di lavoro corrente su quella pipe e il processo genitore legge il risultato sull'altra estremità della pipe, ksh93
virtualizza tutto ciò per nessuno dei due che richiede la forcella né il tubo. Una fork e una pipe verrebbero utilizzate solo per comandi non incorporati.
Si noti che esistono altri contesti che indicano quali shell eseguono il fork di un processo figlio. Ad esempio, per eseguire un comando memorizzato in un eseguibile separato (e che non è uno script destinato allo stesso interprete di shell), una shell dovrebbe eseguire il fork di un processo per eseguire quel comando in esso, altrimenti non lo sarebbe in grado di eseguire più comandi dopo il ritorno di quel comando.
In:
/bin/echo "$((n += 1))"
Non si tratta di una subshell, il comando verrà valutato nell'ambiente di esecuzione della shell corrente, la n
variabile dell'ambiente di esecuzione della shell corrente verrà incrementata, ma la shell eseguirà il fork di un processo figlio per eseguire quel /bin/echo
comando al suo interno con l'espansione di $((n += 1))
come argomento .
Molte shell implementano un'ottimizzazione in quanto non eseguono il fork di un processo figlio per eseguire quel comando esterno se è l'ultimo comando di uno script o di una shell secondaria (per quei subshell che sono implementati come processi figlio). ( bash
tuttavia lo fa solo se quel comando è l'unico comando della subshell).
Ciò significa che, con quelle shell, se l'ultimo comando nella subshell è un comando esterno, la subshell non provoca la generazione di un processo aggiuntivo. Se si confronta:
a=1; /bin/echo "$a"; a=2; /bin/echo "$a"
con
a=1; /bin/echo "$a"; (a=2; /bin/echo "$a")
ci sarà lo stesso numero di processi creati, solo nel secondo caso, il secondo fork viene eseguito in precedenza in modo che a=2
venga eseguito in un ambiente subshell.