¿Cómo puedo detectar si estoy en una subshell?

24

Estoy tratando de escribir una función para reemplazar la funcionalidad del exitincorporado para evitar que salga del terminal.

Intenté usar la SHLVLvariable de entorno pero no parece cambiar dentro de las subcapas:

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

Mi función es la siguiente:

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

Sin exitembargo, esto no me permitirá usar dentro de subcapas:

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

¿Cuál es un buen método para detectar si estoy o no en una subshell?

Jesse_b
fuente
1
Eso es por esto . $ SHLVL es 1, ya que todavía está en el nivel de cáscara de 1 a pesar de que el comando echo $ SHLVL se ejecuta en un "subnivel". Según esa publicación, las subcapas generadas con paréntesis (...)heredan todas las propiedades del proceso primario. Las respuestas proporcionadas son soluciones más sólidas para determinar su nivel de shell.
kemotep
55
@mosvy Siento que esa es una pregunta diferente. Por ejemplo, la BASH_SUBSHELLrespuesta (aunque controvertida) no se aplicaría a esa pregunta.
Sparhawk
2
Vi el título en HNQ y pensé que era una pregunta de mecánica cuántica ...
Mehrdad

Respuestas:

43

En bash, puedes comparar $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

Si no está en bash, $$debería permanecer igual en una subshell, por lo que necesitaría alguna otra forma de obtener su ID de proceso real.

Una forma de obtener su pid real es sh -c 'echo $PPID'. Si lo pone en un plano ( … ), puede parecer que no funciona, ya que su caparazón ha optimizado el tenedor. Pruebe comandos adicionales sin operación ( : ; sh -c 'echo $PPID'; : )para que piense que la subshell es demasiado complicada para optimizarla. El crédito va a John1024 en Stack Overflow para ese enfoque.

derobert
fuente
Es posible que desee cambiar eso a  (sh -c 'echo $PPID'; : )- vea mi comentario sobre la respuesta de John1024 .
G-Man dice 'Reincorporar a Monica' el
@ G-Man Bueno, eso fue solo para probarlo (ya que en el uso real sería algo más complicado) ... pero sí, sería mejor si la prueba funcionara en todos los shells. Así que he puesto un no operativo tanto antes como después, que con suerte manejará todo.
derobert
38

¿Qué tal BASH_SUBSHELL?

BASH_SUBSHELL Se
      incrementa en uno dentro de cada entorno de subshell o subshell cuando el shell
      comienza a ejecutarse en ese entorno. El valor inicial es 0.

$ echo $BASH_SUBSHELL
0
$ (echo $BASH_SUBSHELL)
1
Freddy
fuente
16
Hubiera sido un comando conveniente en la película Inception.
Eric Duminil
Inception es probablemente $ SHLVL
Granny Aching
19

[Esto debería haber sido un comentario, pero mis moderadores tienden a eliminar mis comentarios, por lo que esto quedará como una respuesta que podría usar como referencia incluso si se elimina]

El uso BASH_SUBSHELLes completamente poco confiable ya que solo se establece en 1 en algunas subcapas, no en todas las subcapas.

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

Antes de afirmar que el subproceso en el que se ejecuta un comando de canalización no es una subshell realmente real, considere este man bashfragmento:

Cada comando en una tubería se ejecuta como un proceso separado (es decir, en una subshell).

y las implicaciones prácticas: es esencial si un fragmento de secuencia de comandos se ejecuta en un subproceso o no, no una objeción terminológica.

La única solución, como ya se explicó en las respuestas a esta pregunta, es verificar si $BASHPIDes igual $$o, de manera portátil pero mucho menos eficiente:

if [ "$(exec sh -c 'echo "$PPID"')" != "$$" ]; then
    echo you\'re in a subshell
fi
Mosvy
fuente
11
Nit: BASH_SUBSHELLse establece de manera bastante confiable, pero obtener su valor correctamente es dudoso. Tenga en cuenta lo que dicen los documentos : "Incrementado en uno dentro de cada entorno de subshell o subshell cuando el shell comienza a ejecutarse en ese entorno " . Creo que en el ejemplo de tubería, bash aún no ha comenzado a ejecutarse en ese subshell cuando la variable se expande. Puede comparar echo $BASH_VERSIONcon declare -p BASH_VERSION- este último debe generar de manera confiable 1 con tuberías, trabajos en segundo plano, etc.
muru
66
Incluso digamos, eval 'echo $BASH_SUBSHELL $BASHPID' | catgenerará 1 para BASH_SUBSHELL, porque la variable se expande después de que la ejecución ha comenzado.
Muru
44
todos esos argumentos también deberían aplicarse a la sustitución de procesos y comandos, procesos bg, sin embargo, solo las tuberías son diferentes. Mirando el código, el incremento subshell_levelrealmente se difiere en el caso de las tuberías en primer plano , lo que probablemente tenga alguna razón, pero que no puedo distinguir ;-)
mosvy
2
Tienes razón. Parece que Chet lo pretende explícitamente de esa manera. lists.gnu.org/archive/html/bug-bash/2015-06/msg00050.html : "BASH_SUBSHELL mide (...) subcapas, no elementos de canalización".lists.gnu.org/archive/html/bug-bash/2015-06/msg00054.html : "Voy a pensar si debo documentar el status quo o expandir la definición de 'subshell' que refleja $ BASH_SUBSHELL. "
Muru
2
@JoL te equivocas, la expansión también ocurre en el proceso por separado, lee los enlaces y ejemplos de esta discusión anterior; o simplemente tratar con echo $$ $BASHPID $BASH_SUBSHELL | cat.
Mosvy