Come posso rilevare se sono in una subshell?


24

Sto cercando di scrivere una funzione per sostituire la funzionalità del exitbuiltin per impedirmi di uscire dal terminale.

Ho tentato di utilizzare la SHLVLvariabile d'ambiente ma non sembra cambiare all'interno dei subshells:

$ echo $SHLVL
1
$ ( echo $SHLVL )
1
$ bash -c 'echo $SHLVL'
2

La mia funzione è la seguente:

exit () {
    if [[ $SHLVL -eq 1 ]]; then
        printf '%s\n' "Nice try!" >&2
    else
        command exit
    fi
}

Questo non mi permetterà di usare exitall'interno dei subshells però:

$ exit
Nice try!
$ (exit)
Nice try!

Qual è un buon metodo per rilevare se sono o meno in una subshell?



1
Questo è per questo . $ SHLVL è 1 perché sei ancora nel livello di shell 1 anche se il comando echo $ SHLVL viene eseguito in una "subshell". Secondo tale post, i subshells generati con parentesi (...)ereditano tutte le proprietà del processo parent. Le risposte fornite sono soluzioni più solide per determinare il livello della shell.
kemotep,


5
@mosvy Sento che questa è una domanda diversa. ad es. la BASH_SUBSHELLrisposta (anche se controversa) non si applica a quella domanda.
Sparhawk,

2
Ho visto il titolo su HNQ e ho pensato che fosse una domanda di meccanica quantistica ...
Mehrdad,

Risposte:


43

In bash, è possibile confrontare $BASHPIDa$$

$ ( if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi )
subshell
$   if [ "$$" -eq "$BASHPID" ]; then echo not subshell; else echo subshell; fi
not subshell

Se non sei in bash, $$dovrebbe rimanere lo stesso in una subshell, quindi avresti bisogno di un altro modo per ottenere il tuo ID processo effettivo.

Un modo per ottenere il tuo pid effettivo è sh -c 'echo $PPID'. Se lo metti semplicemente in chiaro ( … ), potrebbe non funzionare, poiché la shell ha ottimizzato la forcella. Prova altri comandi no-op ( : ; sh -c 'echo $PPID'; : )per far pensare che la subshell sia troppo complicata da ottimizzare. Il merito va a John1024 su Stack Overflow per quell'approccio.


Potresti volerlo cambiare in  (sh -c 'echo $PPID'; : )- vedi il mio commento sulla risposta di John1024 .
G-Man dice "Reinstate Monica" il

@G-Man Beh, era solo per testarlo (dal momento che in uso sarebbe stato in qualche modo più complicato) ... ma sì, sarebbe meglio se il test funzionasse in tutte le shell. Quindi ho fatto una no-op sia prima che dopo, sperando che gestisca tutto.
derobert

38

Che ne dici BASH_SUBSHELL?

BASH_SUBSHELL
      Incrementato di uno all'interno di ogni ambiente di subshell o subshell quando la shell
      inizia l'esecuzione in quell'ambiente. Il valore iniziale è 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1

16
Sarebbe stato un comando conveniente nel film Inception.
Eric Duminil,

In Inception è probabilmente $ SHLVL
Granny Aching

19

[questo avrebbe dovuto essere un commento, ma i miei commenti tendono ad essere cancellati dai moderatori, quindi rimarrà come una risposta che potrei usarlo come riferimento anche se cancellato]

L'uso BASH_SUBSHELLè completamente inaffidabile in quanto è impostato solo su 1 in alcuni sottotitoli, non in tutti i sottotitoli.

$ (echo $BASH_SUBSHELL)
1
$ echo $BASH_SUBSHELL | cat
0

Prima di affermare che il sottoprocesso in cui viene eseguito un comando di pipeline non è un subshell realmente reale, considerare questo man bashsnippet:

Ogni comando in una pipeline viene eseguito come un processo separato (cioè in una subshell).

e le implicazioni pratiche: è se un frammento di script viene eseguito o meno un sottoprocesso, il che non è un cavillo di terminologia.

L'unica soluzione, come già spiegato nelle risposte a questa domanda, è verificare se è $BASHPIDuguale $$o, portabilmente ma molto meno efficiente:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi

11
Nit: BASH_SUBSHELLè impostato in modo abbastanza affidabile, ma ottenere il suo valore correttamente è incerto. Nota cosa dicono i documenti : "Incrementato di uno all'interno di ogni ambiente di sottoshell o subshell quando la shell inizia l'esecuzione in quell'ambiente " . Penso che nell'esempio di pipe, bash non abbia ancora iniziato l'esecuzione in quella subshell quando la variabile viene espansa. Puoi confrontare echo $BASH_VERSIONcon declare -p BASH_VERSION- quest'ultimo dovrebbe produrre in modo affidabile 1 con tubi, lavori in background, ecc.
Muru

6
Anche dire, eval 'echo $BASH_SUBSHELL $BASHPID' | catprodurrà 1 per BASH_SUBSHELL, perché la variabile viene espansa dopo l'avvio dell'esecuzione.
Muru,

4
tutti questi argomenti dovrebbero valere anche per la sostituzione di processi e comandi, processi bg, ma sono solo le condutture che sono diverse. Guardando il codice, l'incremento subshell_levelè davvero differito nel caso di condutture in primo piano , che probabilmente ha qualche motivo, ma che non sono in grado di capire ;-)
mosvy

2
Hai ragione. Sembra che Chet lo intenda esplicitamente in quel modo. lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html : "BASH_SUBSHELL misura (...) subshells, non elementi della pipeline." lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html : "Penserò se dovrei documentare lo status quo o espandere la definizione di" subshell "che riflette $ BASH_SUBSHELL. "
Muru,

2
@JoL ti sbagli, l'espansione avviene anche in un processo separato, leggi i link e gli esempi di questa discussione sopra; o prova semplicemente con echo $$ $BASHPID $BASH_SUBSHELL | cat.
mosvy
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.