Como posso detectar se estou em um subshell?

24

Estou tentando escrever uma função para substituir a funcionalidade do exitbuilt-in para me impedir de sair do terminal.

Eu tentei usar a SHLVLvariável de ambiente, mas ela não parece mudar nos subshells:

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

Minha função é a seguinte:

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

Isso não permitirá que eu use exitdentro de subshells:

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

Qual é um bom método para detectar se estou ou não em um subshell?

Jesse_b
fonte
11
Isso é por causa disso . $ SHLVL é 1 porque você ainda está no nível 1 do shell, mesmo que o comando echo $ SHLVL seja executado em um "subshell". De acordo com esse post, subshells gerados entre parênteses (...)herdam todas as propriedades do processo pai. As respostas fornecidas são soluções mais robustas para determinar o nível do seu shell.
kemotep 12/06
11
Possível duplicata de Como posso obter o pid de um subshell?
mosvy 13/06
5
@mosvy Eu sinto que essa é uma pergunta diferente. por exemplo, a BASH_SUBSHELLresposta (mesmo que controversa) não se aplicaria a essa pergunta.
Sparhawk
2
Vi o título no HNQ e achei que era uma questão de mecânica quântica ...
Mehrdad

Respostas:

43

No bash, você pode comparar $BASHPIDcom$$

$ ( 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 você não está no bash, $$deve permanecer o mesmo em um subshell, portanto, você precisará de outra maneira de obter seu ID de processo real.

Uma maneira de obter seu pid real é sh -c 'echo $PPID'. Se você simplesmente colocar isso em uma planície ( … ), pode parecer que não funciona, pois o seu shell otimizou o garfo. Tente comandos extras no-op ( : ; sh -c 'echo $PPID'; : )para fazê-lo pensar que o subshell é muito complicado para otimizar. O crédito vai para John1024 no Stack Overflow para essa abordagem.

derobert
fonte
Você pode mudar isso para  (sh -c 'echo $PPID'; : )- veja meu comentário na resposta de John1024 .
G-Man diz 'Reinstate Monica'
@ G-Man Bem, isso foi apenas para testá-lo (já que em uso real seria algo muito mais complicado) ... mas sim, seria melhor se o teste funcionasse em todas as conchas. Então, eu coloquei um no-op antes e depois, que esperamos que resolva tudo.
derobert 19/06
38

Que tal BASH_SUBSHELL?

BASH_SUBSHELL
      Incrementado por um em cada ambiente subshell ou subshell quando o shell
      começa a executar nesse ambiente. O valor inicial é 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
Freddy
fonte
16
Teria sido um comando conveniente no filme Inception.
Eric Duminil 13/06
No início, é provavelmente $ SHLVL
Granny Aching
19

[isso deveria ter sido um comentário, mas meus comentários tendem a ser excluídos pelos moderadores; portanto, isso permanecerá como uma resposta que eu poderia usar como referência, mesmo se excluído]

O uso não BASH_SUBSHELLé totalmente confiável, pois só pode ser definido como 1 em alguns subconjuntos, não em todos os subconjuntos.

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

Antes de afirmar que o subprocesso em que um comando de pipeline é executado não é um subshell realmente real, considere este man bashtrecho:

Cada comando em um pipeline é executado como um processo separado (isto é, em um subshell).

e as implicações práticas - é se um fragmento de script é executado em um subprocesso ou não, o que é essencial, e não uma queixa de terminologia.

A única solução, como já explicado nas respostas a esta pergunta, é verificar se $BASHPIDé igual $$ou, de maneira portável mas muito menos eficiente:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi
mosvy
fonte
11
Nit: BASH_SUBSHELLestá definido de maneira bastante confiável, mas obter seu valor corretamente é duvidoso. Observe o que os documentos dizem: "Incrementado por um em cada ambiente de subshell ou subshell quando o shell começa a ser executado nesse ambiente. " Acho que no exemplo de pipe, o bash ainda não começou a executar nesse subshell quando a variável é expandida. Você pode comparar echo $BASH_VERSIONcom declare -p BASH_VERSION- este último deve confiantemente saída 1 com tubos, trabalhos de fundo, etc.
Muru
6
Mesmo dizer, eval 'echo $BASH_SUBSHELL $BASHPID' | catproduzirá 1 para BASH_SUBSHELL, porque a variável é expandida após o início da execução.
muru 13/06
4
todos esses argumentos também devem se aplicar à substituição de processos e comandos, processos bg, mas são apenas os pipelines que são diferentes. Olhando para o código, o incremento subshell_levelrealmente é adiado no caso de pipelines em primeiro plano , o que provavelmente tem algum motivo, mas que não sou capaz de entender ;-)
mosvy
2
Você está certo. Parece que Chet pretende explicitamente dessa maneira. lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html : "BASH_SUBSHELL mede (...) sub -conchas, não elementos de pipeline". lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html : "Vou pensar se devo documentar o status quo ou expandir a definição de` subshell '' que $ BASH_SUBSHELL reflete. "
muru 13/06
2
@ JoL, você está errado, a expansão também acontece em um processo separado. Por favor, leia os links e exemplos desta discussão acima; ou apenas tente com echo $$ $BASHPID $BASH_SUBSHELL | cat.
mosvy 13/06