Creazione di subshell Bash con parentesi graffe


31

In base a ciò , posizionare un elenco di comandi tra parentesi graffe fa sì che l'elenco venga eseguito nel contesto della shell corrente. Non viene creata alcuna subshell .

Usando psper vedere questo in azione

Questa è la gerarchia dei processi per una pipeline del processo eseguita direttamente sulla riga di comando. 4398 è il PID per la shell di accesso:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

Ora segue la gerarchia dei processi per una pipeline del processo tra parentesi graffe eseguita direttamente sulla riga di comando. 4398 è il PID per la shell di accesso. È simile alla gerarchia sopra che dimostra che tutto viene eseguito nel contesto della shell corrente :

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

Ora, questa è la gerarchia dei processi quando la stessa sleepnella pipeline viene posizionata all'interno di parentesi graffe (quindi due livelli di parentesi graffe in tutto)

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

Perché bashè necessario creare una subshell da eseguire sleepnel terzo caso quando la documentazione afferma che i comandi tra parentesi graffe vengono eseguiti nel contesto della shell corrente?


Interessante, immagino che sia perché nel terzo caso il gruppo interno fa parte della pipeline, e quindi viene eseguito in sotto-shell, ad esempio, come qualsiasi altra chiamata di funzione che fa parte della pipeline. Ha senso?
Miroslav Koškár,

2
Non direi "La shell deve" solo perché lo fa ... Le pipeline non vengono eseguite nel contesto della shell. Se la pipeline non è composta da comandi esterni, è sufficiente creare sottoprocessi. { sleep 2 | command ps -H; }
Hauke ​​Laging,

Risposte:


26

In una pipeline, tutti i comandi vengono eseguiti contemporaneamente (con il loro stdout / stdin collegato da pipe), quindi in processi diversi.

Nel

cmd1 | cmd2 | cmd3

Tutti e tre i comandi vengono eseguiti in processi diversi, quindi almeno due di essi devono essere eseguiti in un processo figlio. Alcune shell eseguono una di esse nel processo di shell corrente (se incorporato come reado se la pipeline è l'ultimo comando dello script), ma le bashesegue tutte nel proprio processo separato (tranne con l' lastpipeopzione nelle bashversioni recenti e in alcune condizioni specifiche ).

{...}comandi di gruppi. Se quel gruppo fa parte di una pipeline, deve essere eseguito in un processo separato proprio come un semplice comando.

Nel:

{ a; b "$?"; } | c

Abbiamo bisogno di una shell per valutare che a; b "$?"è un processo separato, quindi abbiamo bisogno di una subshell. La shell potrebbe ottimizzare non forking bpoiché è l'ultimo comando da eseguire in quel gruppo. Alcune conchiglie lo fanno, ma apparentemente no bash.


" Tutti e tre i comandi vengono eseguiti in processi diversi, quindi almeno due di essi devono essere eseguiti in una subshell. " Perché in questo caso è necessaria una subshell? La shell padre non può generare processi ad albero? Oppure, quando dici "devi correre in una subshell ", vuoi dire che la shell si biforcherà da sola, e quindi eseguirà per ogni cmd1 cmd2 e cmd3? Se eseguo questo, bash -c "sleep 112345 | cat | cat "vedo solo una bash creata e quindi 3 figli senza altre sub-baslea intercalate.
Hakan Baba,

" Abbiamo bisogno di una shell per valutare che a; b" $? "È un processo separato, quindi abbiamo bisogno di una subshell. " Potresti anche espandere il ragionamento? Perché abbiamo bisogno di una sottosella per capirlo? Cosa serve per capirlo? Presumo che l'analisi sia richiesta, ma cos'altro? . La shell del genitore non può analizzare a; b "$?"? C'è davvero un bisogno fondamentale di una sottosopra, o forse è una decisione di progettazione / implementazione su bash?
Hakan Baba,

@HakanBaba, l'ho cambiato in "processo figlio" per evitare potenziali confusioni.
Stéphane Chazelas,

1
@HakanBaba, l'analisi viene eseguita nel genitore (il processo che legge il codice, quello che ha eseguito l'interprete a meno che non sia stato passato quel codice eval), ma la valutazione (esegui il primo comando, attendi, esegui il secondo) è fatto nel bambino, quello che ha stdout collegato al tubo.
Stéphane Chazelas,

nella { sleep 2 | ps -H; }bash genitore vede sleep 2che richiede un fork / exec. Ma nella { { sleep 2; } | ps -H; }bash genitore vede { sleep 2; }in altre parole, un po 'di codice bash. Sembra che il genitore possa gestire il fork / exec sleep 2ma genera un nuovo bash ricorsivamente per gestire il codice bash rilevato. Questa è la mia comprensione, ha senso?
Hakan Baba,

19

La nidificazione delle parentesi graffe sembra indicare che stai creando un livello aggiuntivo di scoping che richiede l'invocazione di una nuova sotto-shell. Puoi vedere questo effetto con la seconda copia di Bash nel tuo ps -Houtput.

Solo i processi previsti nel primo livello di parentesi graffe vengono eseguiti nell'ambito della shell Bash originale. Eventuali parentesi graffe nidificate verranno eseguite nella propria shell Bash con ambito.

Esempio

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

Eliminando il | ps -Hmix solo per poter vedere le parentesi graffe nidificate, possiamo correre ps auxf | lessin un'altra shell.

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

Ma aspetta, c'è di più!

Se togli le pipe e usi questa forma di comando, vediamo cosa ti aspetteresti davvero:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

Ora nella finestra di controllo risultante riceviamo un aggiornamento ogni 2 secondi di quello che sta succedendo:

Ecco il primo sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

Ecco il secondo sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

Ecco il terzo sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

Si noti che tutti e tre i dormienti, sebbene invocati a diversi livelli di nidificazione di parentesi graffe, rimangono all'interno del PID 5676 di Bash. Quindi credo che il tuo problema sia autoinflitto con l'uso di | ps -H.

conclusioni

L'uso di | ps -H(ovvero la pipe) sta causando una sub-shell aggiuntiva, quindi non usare quel metodo quando si tenta di interrogare cosa sta succedendo.


quindi, "Solo i processi previsti nel primo livello di parentesi graffe vengono eseguiti nell'ambito della shell Bash originale."
xealits,

@xealits - è un follow-up Q mi stai chiedendo?
slm

@slm è solo l'enfasi sul punto principale della risposta, come l'ho vista. I comandi nel primo livello di parentesi graffe vengono eseguiti nella shell corrente, le parentesi graffe nidificate creano nuove shell. Le parentesi si differenziano per la creazione di subshell subito, al primo livello. Se ho sbagliato, correggimi. Ma, come altri sottolineano, la domanda iniziale ha anche condutture. Da qui la creazione di processi separati. E le parentesi graffe devono creare una shell quando vengono utilizzate per un processo separato. Quindi, probabilmente, è la ragione del comportamento in questione.
Xealits,

ora, dopo aver riletto il tuo post, vedo che mi sbagliavo: la dichiarazione di nidificazione riguarda solo il caso delle condutture. Quindi, le parentesi graffe non creano mai nuove conchiglie, a meno che non si avvolga un processo separato con loro - quindi devono farlo.
Xealits,

@xealits - è corretto.
slm

7

Pubblicherò i risultati dei miei test, il che mi porta a concludere che bash crea una sotto-shell per un comando di gruppo se e solo se fa parte della pipeline, è simile come se si chiamasse una funzione che sarebbe anche chiamata nella sotto-shell.

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1

Anche la mia A lo sta mostrando.
slm
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.