Le parentesi mettono davvero il comando in una subshell?


94

Da quello che ho letto, inserire un comando tra parentesi dovrebbe eseguirlo in una subshell, simile all'esecuzione di uno script. Se questo è vero, come vede la variabile x se x non viene esportato?

x=1

L'esecuzione (echo $x)sulla riga di comando risulta in 1

L'esecuzione echo $xin uno script non comporta nulla, come previsto

Risposte:


134

Una subshell inizia come una copia quasi identica del processo di shell originale. Sotto il cofano, la shell chiama la forkchiamata di sistema 1 , che crea un nuovo processo il cui codice e memoria sono copie 2 . Quando viene creata la subshell, ci sono pochissime differenze tra essa e il suo genitore. In particolare, hanno le stesse variabili. Anche la $$variabile speciale mantiene lo stesso valore nei subshells: è l'ID del processo della shell originale. Allo stesso modo $PPIDè il PID del genitore della shell originale.

Alcune shell cambiano alcune variabili nella subshell. Bash imposta BASHPIDil PID del processo shell, che cambia in subshells. Bash, zsh e mksh provvedono $RANDOMa produrre valori diversi nel genitore e nella subshell. Ma a parte casi speciali incorporati come questi, tutte le variabili hanno lo stesso valore nella subshell come nella shell originale, lo stesso stato di esportazione, lo stesso stato di sola lettura, ecc. Tutte le definizioni di funzione, definizioni di alias, opzioni di shell e anche altre impostazioni vengono ereditate.

Una subshell creata da (…)ha gli stessi descrittori di file del suo creatore. Alcuni altri mezzi per creare subshells modificano alcuni descrittori di file prima di eseguire il codice utente; ad esempio, il lato sinistro di una pipe viene eseguito in una subshell 3 con output standard collegato alla pipe. La subshell inizia anche con la stessa directory corrente, la stessa maschera di segnale, ecc. Una delle poche eccezioni è che le subshell non ereditano le trap personalizzate: i segnali ignorati ( ) rimangono ignorati nella subshell, ma le altre trap ( SIGNAL ) vengono ripristinate all'azione predefinita 4 .trap '' SIGNALtrap CODE

Una subshell è quindi diversa dall'esecuzione di uno script. Uno script è un programma separato. Questo programma separato potrebbe anche essere uno script che viene eseguito dallo stesso interprete del genitore, ma questa coincidenza non dà al programma separato alcuna visibilità speciale sui dati interni del genitore. Le variabili non esportate sono dati interni, quindi quando viene eseguito l'interprete per lo script della shell figlio , queste variabili non vengono visualizzate. Le variabili esportate, ovvero le variabili di ambiente, vengono trasmesse ai programmi eseguiti.

Così:

x=1
(echo $x)

stampa 1perché la subshell è una replica della shell che l'ha generata.

x=1
sh -c 'echo $x'

capita di eseguire una shell come processo figlio di una shell, ma xsulla seconda riga non c'è più connessione con xla seconda riga rispetto a

x=1
perl -le 'print $x'

o

x=1
python -c 'print x'

1 Un'eccezione è la ksh93shell in cui il fork è ottimizzato e la maggior parte dei suoi effetti collaterali sono emulati.
2 Semanticamente, sono copie. Dal punto di vista dell'implementazione, c'è molta condivisione in corso.
3 Per il lato destro, dipende dalla shell.
4 Se lo provi, nota che cose come$(trap) potrebbero riportare le trappole della shell originale. Nota anche che molte shell hanno bug in casi angolari che coinvolgono trappole. Ad esempio, ninjalj nota che a partire dalla bash 4.3, bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'esegue la ERRtrap dalla subshell nidificata nel caso "due subshells", ma non la ERRtrap dalla subshell intermedia - l' set -Eopzione dovrebbe propagare ilERRtrap a tutti i subshells ma la subshell intermedia è ottimizzata e quindi non è lì per eseguire la sua ERRtrap.


2
@Kusalananda No. ( x=out; (x=in; echo $x))
Gilles

2
@ flow2k Questo è l'ordine di espansione per le cose che accadono allo stesso livello. Ma devi anche considerare come l'espansione si mescola con la valutazione. Quando l'espansione richiede la valutazione di un costrutto nidificato, il costrutto interno viene valutato per primo. Quindi, per esempio, per valutare echo $(x=2; echo $x), il frammento $(x=2; echo $x)deve essere espanso. Ciò richiede una valutazione del comando x=2; echo $x. L'espansione di $xavviene durante questa valutazione, dopo aver valutato la parte x=2.
Gilles

2
@ flow2k Non esiste alcun ordine tra l'espansione dei parametri e la sostituzione dei comandi. Nota che questa frase usa i punti e virgola per separare i passaggi di espansione, ma l'espansione dei parametri e la sostituzione dei comandi sono nella stessa clausola delimitata da punti e virgola (sì, è sottile). L'ordine è importante quando una delle parti ha un effetto collaterale che influisce sull'altra parte, ad es. (Con xnon impostato) echo $(echo foo >somefile)${x-$(cat somefile)}o echo $(echo $x),${x=1}.
Gilles

1
@Gilles; Sono confuso. Se una subshell è diversa dall'esecuzione di uno script, allora perché si dice che: L' esecuzione di uno script shell avvia un nuovo processo, una subshell. ? Inoltre, deve essere creato un ambiente subshell come duplicato dell'ambiente shell . Pertanto, ./file verrà eseguito in ambiente subshell e quindi dovrebbe ereditare i parametri della shell impostati dall'assegnazione delle variabili.
Hawcks

2
@haccks La definizione nell'ABS è un'approssimazione, e non molto buona. Gli esempi sono buoni, ma le prime due righe di quella pagina sono così semplificate da sbagliare. L'esecuzione di uno script da un altro script avvia un nuovo processo che non è una subshell. Nel SUS, le definizioni sono corrette (ma non sempre molto facili da capire). ./filenon viene eseguito in una subshell. Vedi anche unix.stackexchange.com/q/261638 e unix.stackexchange.com/a/157962
Gilles

15

Ovviamente sì, come dice tutta la documentazione, un comando tra parentesi viene eseguito in una subshell.

La subshell eredita una copia di tutte le variabili del genitore. La differenza è che tutte le modifiche apportate nella subshell non vengono apportate anche nel genitore.

La pagina man di ksh lo rende un po 'più chiaro di quello bash:

man ksh:

Un comando tra parentesi viene eseguito in una sotto-shell senza rimuovere le variabili non esportate.

man bash:

(elenco)

l'elenco viene eseguito in un ambiente subshell (vedere AMBIENTE DI ESECUZIONE DEI COMANDI di seguito). Le assegnazioni di variabili e i comandi integrati che influiscono sull'ambiente della shell non rimangono attivi dopo il completamento del comando.

AMBIENTE DI ESECUZIONE DEI COMANDI

La shell ha un ambiente di esecuzione, che consiste nei seguenti [...] parametri della shell che sono impostati dall'assegnazione variabile [...].
Sostituzione dei comandi, comandi raggruppati con parentesi e comandi asincroni vengono richiamati in un ambiente subshell che è un duplicato dell'ambiente shell, [...]


3
Ciò deve essere contrastato When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following., che contiene l'elemento: · shell variables and functions marked for export, along with variables exported for the command, passed in the environment(dalla stessa man bashsezione) che spiega perché uno echo $xscript non stampa nulla se xnon viene esportato.
Johan E,
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.