Regola per invocare subshell in Bash?


24

Mi sembra di fraintendere la regola di Bash per la creazione di una subshell. Ho pensato che le parentesi crea sempre una subshell, che funziona come un suo processo.

Tuttavia, questo non sembra essere il caso. Nel frammento di codice A (sotto), il secondo sleepcomando non viene eseguito in una shell separata (come determinato da pstreein un altro terminale). Tuttavia, nello Snippet di codice B, il secondo sleepcomando viene eseguito in una shell separata. L'unica differenza tra gli snippet è che il secondo snippet ha due comandi tra parentesi.

Qualcuno potrebbe spiegare la regola per quando vengono creati i subshell?

CODICE SNIPPET A:

sleep 5
(
sleep 5
)

CODICE SNIPPET B:

sleep 5
(
x=1
sleep 5
)

Risposte:


20

Le parentesi iniziano sempre una subshell. Quello che sta succedendo è che bash rileva che sleep 5è l'ultimo comando eseguito da quella subshell, quindi chiama execinvece di fork+ exec. Il sleepcomando sostituisce la subshell nello stesso processo.

In altre parole, il caso base è:

  1. ( … )creare una subshell. Il processo originale chiama forke wait. Nel sottoprocesso, che è una subshell:
    1. sleepè un comando esterno che richiede un sottoprocesso del sottoprocesso. La subshell chiama forke wait. Nel processo secondario:
      1. Il processo secondario esegue il comando esterno → exec.
      2. Alla fine il comando termina → exit.
    2. wait viene completato nella subshell.
  2. wait completa nel processo originale.

L'ottimizzazione è:

  1. ( … )creare una subshell. Il processo originale chiama forke wait. Nel sottoprocesso, che è una subshell fino a quando non chiama exec:
    1. sleep è un comando esterno ed è l'ultima cosa che questo processo deve fare.
    2. Il sottoprocesso esegue il comando esterno → exec.
    3. Alla fine il comando termina → exit.
  2. wait completa nel processo originale.

Quando aggiungi qualcos'altro dopo la chiamata sleep, è necessario mantenere la subshell, quindi questa ottimizzazione non può avvenire.

Quando aggiungi qualcos'altro prima della chiamata sleep, l'ottimizzazione potrebbe essere fatta (e ksh lo fa), ma bash non lo fa (è molto conservativo con questa ottimizzazione).


Il subshell viene creato chiamando forke il processo figlio viene creato (per eseguire comandi esterni) chiamandofork + exec . Ma il tuo primo para suggerisce che fork + execè chiamato anche subshell. Cosa sto sbagliando qui?
Hawcks

1
@haccks fork+ execnon è chiamato per la subshell, si chiama per il comando esterno. Senza alcuna ottimizzazione, c'è una forkchiamata per la subshell e un'altra per il comando esterno. Ho aggiunto una descrizione dettagliata del flusso alla mia risposta.
Gilles 'SO- smetti di essere malvagio' il

Grazie mille per l'aggiornamento. Ora spiega meglio. Ne posso dedurre che in caso di (...)(nel caso di base), potrebbe esserci o meno una chiamata a execseconda che la subshell abbia qualche comando esterno da eseguire, mentre in caso di esecuzione di qualsiasi comando esterno ci deve essere fork + exec.
Hawcks

Un'altra domanda: questa ottimizzazione funziona solo per subshell o può essere fatta per un comando come datein una shell?
Hawcks

@haccks Non capisco la domanda. Questa ottimizzazione riguarda l'invocazione di un comando esterno come l'ultima cosa che fa un processo di shell. Non è limitato ai subshells: confronta strace -f -e clone,execve,write bash -c 'date'estrace -f -e clone,execve,write bash -c 'date; true'
SO di Gilles

4

Dalla Guida alla programmazione avanzata di Bash :

"In generale, un comando esterno in uno script esclude un sottoprocesso, mentre un built-in di Bash no. Per questo motivo, i built-in vengono eseguiti più rapidamente e utilizzano meno risorse di sistema rispetto ai loro equivalenti di comando esterni."

E un po 'più in basso:

"Un elenco di comandi incorporato tra parentesi viene eseguito come subshell."

Esempi:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

Esempio di utilizzo del codice OP (con dormienti più brevi perché sono impaziente):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

L'output:

[root@talara test]# bash sub_bash
6606
6608
6608

2
Grazie per la risposta Tim. Non sono sicuro che risponda pienamente alla mia domanda però. Dal momento che "Un elenco di comandi incorporato tra parentesi viene eseguito come una subshell", mi aspetto che il secondo sleepvenga eseguito in una subshell (forse sul processo della subshell poiché è incorporato, anziché un sottoprocesso della subshell). Tuttavia, in ogni caso, mi sarei aspettato che esistesse una subshell, ovvero un sottoprocesso di Bash nell'ambito del processo Bash padre. Per lo Snippet B sopra, questo non sembra essere il caso.
timido

Correzione: poiché sleepnon sembra essere un built-in, mi aspetto che la seconda sleepchiamata in entrambi i frammenti venga eseguita in un sottoprocesso del processo subshell.
timoroso

@bashful Mi sono preso la libertà di hackerare il tuo codice con la mia $BASHPIDvariabile. Purtroppo il modo in cui lo stavi facendo non ti stava dando l'intera storia in cui credo. Vedi il mio output aggiunto nella risposta.
Tim

4

Una nota aggiuntiva alla risposta di @Gilles.

Come detto da Gilles: The parentheses always start a subshell.

Tuttavia, i numeri che hanno tali sotto-shell potrebbero ripetere:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

Come puoi vedere, $$ continua a ripetersi, ed è come previsto, perché (esegui questo comando per trovare la man bashriga corretta ):

$ LESS=+/'^ *BASHPID' man bash

BASHPID Si
espande nell'ID processo del processo bash corrente. Ciò differisce dal $$ in determinate circostanze, come i subshells che non richiedono la reinizializzazione di bash.

Cioè: se la shell non viene reinizializzata, $$ è lo stesso.

O con questo:

$ LESS=+/'^ *Special Parameters' man bash

Parametri speciali
$ Si espande nell'ID processo della shell. In una subshell (), si espande nell'ID del processo della shell corrente, non nella subshell.

L' $$ID della shell corrente (non la subshell).


1
Bel trucco per aprire la manpage di bash in una sezione specifica
Daniel Serodio,
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.