¿Cuántas conchas tengo de profundidad?

73

Problema : Encuentra cuántas conchas de profundidad tengo.

Detalles : abro mucho el shell desde vim. Construye, corre y sal. A veces olvido y abro otro vim dentro y luego otro shell. :(

Quiero saber cuántos shells de profundidad tengo, tal vez incluso tenerlo en mi pantalla de shell en todo momento. (Puedo manejar esa parte).

Mi solución : analizar el árbol de procesos y buscar vim y bash / zsh y descubrir la profundidad del proceso actual dentro de él.

¿Ya existe algo así? No pude encontrar nada.

Pranay
fuente
27
¿Es la $SHLVLvariable (mantenida por varios shells) lo que estás buscando?
Stéphane Chazelas
1
Para aclarar, no está realmente interesado en cuántos shells (directamente anidados), indicados por SHLVL, pero si su shell actual es un descendiente de vim?
Jeff Schaller
14
Esto parece ser un problema de XY: mi flujo de trabajo es ^ Z para escapar de la instancia de vim en el shell principal y fgvolver, que no tiene este problema.
Pomo
2
@Pomo de la puerta, yo también hago eso. pero prefiero este, porque entonces tendría que seguir revisando "trabajos". y puede haber muchas ejecuciones a veces en mi máquina. ahora, agregue TMUX con la ecuación. Se vuelve complejo y desbordante. Si engendro shell dentro de vim, sería menos disperso. (Sin embargo, termino haciendo el desastre y de ahí la pregunta).
Pranay
3
@Pomo de la puerta: con el debido respeto, parece responder a la pregunta "¿Cómo conduzco desde el punto A desde el punto B?" Con la sugerencia "No conduzca; simplemente tome un Uber ”. Si el usuario tiene un flujo de trabajo que implica editar múltiples archivos al mismo tiempo, tener múltiples vimtrabajos detenidos en paralelo puede ser más confuso que tener una pila de procesos anidados. Por cierto, prefiero tener varias ventanas , por lo que puedo saltar rápidamente hacia adelante y hacia atrás rápidamente, pero no llamaría a esto un problema XY solo porque prefiero un flujo de trabajo diferente.
Scott

Respuestas:

45

Cuando leí tu pregunta, mi primer pensamiento fue $SHLVL. Entonces vi que querías contar vimniveles además de los niveles de shell. Una manera simple de hacer esto es definir una función de shell:

vim()  { ( ((SHLVL++)); command vim  "$@");}

Esto aumentará automática y silenciosamente SHLVL cada vez que escriba un vimcomando. Deberá hacer esto para cada variante de vi/ vimque alguna vez use; p.ej,

vi()   { ( ((SHLVL++)); command vi   "$@");}
view() { ( ((SHLVL++)); command view "$@");}

El conjunto externo de paréntesis crea una subshell, por lo que el cambio manual en el valor de SHLVL no contamina el entorno de shell actual (principal). Por supuesto, la commandpalabra clave está allí para evitar que las funciones se llamen a sí mismas (lo que daría como resultado un bucle de recursión infinito). Y, por supuesto, debe poner estas definiciones en su .bashrcu otro archivo de inicialización de shell.


Hay una leve ineficiencia en lo anterior. En algunas conchas (bash siendo uno), si dices

( cmd 1 ;  cmd 2 ;;  cmd n )

donde es un programa externo ejecutable (es decir, no un comando incorporado), el shell mantiene un proceso adicional por ahí, solo para esperar a que termine. Esto (posiblemente) no es necesario; Las ventajas y desventajas son discutibles. Si no le importa atar un poco de memoria y una ranura de proceso (y ver un proceso de shell más de lo que necesita cuando hace una ), haga lo anterior y pase a la siguiente sección. Lo mismo si estás usando un shell que no mantiene el proceso extra por ahí. Pero, si desea evitar el proceso adicional, lo primero que debe intentar escmdncmdnps

vim()  { ( ((SHLVL++)); exec vim  "$@");}

El execcomando está allí para evitar que el proceso de shell adicional se demore.

Pero, hay una trampa. El manejo del shell SHLVLes algo intuitivo: cuando se inicia el shell, comprueba si SHLVLestá configurado. Si no está establecido (o establecido en algo que no sea un número), el shell lo establece en 1. Si está establecido (en un número), el shell agrega 1 a él.

Pero, según esta lógica, si dices exec sh, SHLVLdeberías subir. Pero eso no es deseable, porque su nivel real de shell no ha aumentado. El shell maneja esto restando uno de SHLVL cuando haces un exec:

$ echo "$SHLVL"
1

$ set | grep SHLVL
SHLVL=1

$ env | grep SHLVL
SHLVL=1

$ (env | grep SHLVL)
SHLVL=1

$ (env) | grep SHLVL
SHLVL=1

$ (exec env) | grep SHLVL
SHLVL=0

Entonces

vim()  { ( ((SHLVL++)); exec vim  "$@");}

es un lavado; se incrementa SHLVLsolo para disminuirlo nuevamente. También podría decir vim, sin el beneficio de una función.

Nota:
De acuerdo con Stéphane Chazelas (quien lo sabe todo) , algunos shells son lo suficientemente inteligentes como para no hacer esto si execestán en un subshell.

Para arreglar esto, harías

vim()  { ( ((SHLVL+=2)); exec vim  "$@");}

Entonces vi que querías contar los vimniveles independientemente de los niveles de shell. Bueno, exactamente el mismo truco funciona (bueno, con una pequeña modificación):

vim() { ( ((SHLVL++, VILVL++)); export VILVL; exec vim "$@");}

(y así sucesivamente para vi, viewetc.) exportEs necesario porque VILVLno está definido como una variable de entorno de forma predeterminada. Pero no necesita ser parte de la función; se puede decir simplemente export VILVLcon un comando aparte (en su .bashrc). Y, como se discutió anteriormente, si el proceso de shell adicional no es un problema para usted, puede hacerlo en command vimlugar de exec vimdejarlo SHLVLsolo:

vim() { ( ((VILVL++)); command vim "$@");}

Preferencia personal:
es posible que desee cambiar el nombre VILVLde algo como VIM_LEVEL. Cuando miro a " VILVL", me duelen los ojos; no pueden decir si se trata de un error ortográfico de "vinilo" o de un número romano mal formado.


Si está utilizando un shell que no es compatible SHLVL(por ejemplo, guión), puede implementarlo usted mismo siempre que el shell implemente un archivo de inicio. Solo haz algo como

if [ "$SHELL_LEVEL" = "" ]
then
    SHELL_LEVEL=1
else
    SHELL_LEVEL=$(expr "$SHELL_LEVEL" + 1)
fi
export SHELL_LEVEL

en su .profileo archivo aplicable. (Probablemente no debería usar el nombre SHLVL, ya que eso causará caos si alguna vez comienza a usar un shell que lo admita SHLVL).


Otras respuestas han abordado el problema de incrustar valores variables de entorno en su indicador de shell, por lo que no repetiré eso, especialmente si usted dice que ya sabe cómo hacerlo.

Scott
fuente
1
Me sorprende un poco que tantas respuestas sugieran ejecutar un programa ejecutable externo, como pso pstree, cuando puede hacer esto con shell incorporado.
Scott
Esta respuesta es perfecta. He marcado esto como la solución (desafortunadamente todavía no tiene tantos votos).
Pranay
Su enfoque es sorprendente y está utilizando solo las primitivas, lo que significa que incluir esto en mi .profile / .shellrc no rompería nada. Los pongo en cualquier máquina en la que trabajo.
Pranay
1
Tenga en cuenta que dashtiene expansión aritmética. SHELL_LEVEL=$((SHELL_LEVEL + 1))debería ser suficiente incluso si $ SHELL_LEVEL estaba previamente desarmado o vacío. Es sólo si tiene que ser portátil para el shell Bourne que se necesitaría recurrir a expr, pero entonces también tendría que reemplazar $(...)con `..`. SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Stéphane Chazelas
2
@Pranay, es poco probable que sea un problema. Si un atacante puede inyectar cualquier env arbitraria, entonces cosas como PATH / LD_PRELOAD son opciones más obvias, pero si las variables no problemáticas pasan, como con sudo configurado sin reset_env (y uno puede forzar un bashscript para leer ~ / .bashrc por haciendo stdin un socket por ejemplo), entonces eso puede convertirse en un problema. Eso es un montón de "si", pero algo para tener en cuenta (los datos no saneados en contexto aritmético son peligrosos)
Stéphane Chazelas
37

Puede contar la cantidad de tiempo que necesita para subir el árbol de procesos hasta que encuentre un líder de sesión. Al igual que zshen Linux:

lvl() {
  local n=0 pid=$$ buf
  until
    IFS= read -rd '' buf < /proc/$pid/stat
    set -- ${(s: :)buf##*\)}
    ((pid == $4))
  do
    ((n++))
    pid=$2
  done
  echo $n
}

O POSIXly (pero menos eficiente):

lvl() (
  unset IFS
  pid=$$ n=0
  until
    set -- $(ps -o ppid= -o sid= -p "$pid")
    [ "$pid" -eq "$2" ]
  do
    n=$((n + 1)) pid=$1
  done
  echo "$n"
)

Eso daría 0 para el shell que inició su emulador de terminal o getty y uno más para cada descendiente.

Solo necesita hacer eso una vez al inicio. Por ejemplo con:

PS1="[$(lvl)]$PS1"

en su ~/.zshrco equivalente para tenerlo en su solicitud.

tcshy varios otros proyectiles ( zsh, ksh93, fishy bashpor lo menos) mantienen una $SHLVLvariable que se incrementan en el inicio (y decremento antes de ejecutar otro comando con exec(a no ser que execse encuentra en un subnivel si no están con errores (pero muchos son))). Sin embargo, eso solo rastrea la cantidad de anidamiento de shell , no el proceso de anidación. Tampoco se garantiza que el nivel 0 sea el líder de la sesión.

Stéphane Chazelas
fuente
Sí ... esto o similar. No deseaba escribir esto por mi cuenta, y no era mi intención que alguien escribiera esto por mí. :(. Esperaba alguna característica en vim o shell o algún complemento que se mantiene regularmente. Busqué pero no encontré algo.
Pranay
31

Utilizar echo $SHLVL. Utiliza el principio KISS . Dependiendo de la complejidad de su programa, esto puede ser suficiente.

user2497
fuente
2
Funciona para bash, pero no para dash.
agc
SHLVL no me ayuda. Lo sabía, y también apareció en la búsqueda cuando busqué. :) Hay más detalles en la pregunta.
Pranay
@Pranay ¿Está seguro de que vim en sí no proporciona esta información?
user2497
@ user2497, estoy un poco. Esta es la premisa de la pregunta. Busqué en todas partes, también estaba al tanto de SHLVL. Quería -> a) estar seguro de que no existe tal cosa. b) hacerlo con la menor cantidad de dependencias / mantenimiento.
Pranay
16

Una posible solución es mirar la salida de pstree. Cuando se ejecuta dentro de una concha que se generó desde adentro vi, la parte del árbol del árbol que se enumera pstreedebe mostrarle qué tan profundo está. Por ejemplo:

$ pstree <my-user-ID>
...
       ├─gnome-terminal-─┬─bash───vi───sh───vi───sh───pstree
...
John
fuente
Sí, eso es lo que sugerí como solución (en la pregunta). Sin embargo, no deseo analizar el pstree :(. Esto es bueno para leerlo manualmente, estaba pensando en escribir un programa para hacerlo por mí y avisarme. No estoy muy inclinado a escribir un analizador si un complemento / herramienta ya lo hace :).
Pranay
11

Primera variante: solo profundidad de caparazón.

Solución simple para bash: agregar a las .bashrcsiguientes dos líneas (o cambiar su PS1valor actual ):

PS1="${SHLVL} \w\$ "
export PS1

Resultado:

1 ~$ bash
2 ~$ bash
3 ~$ exit
exit
2 ~$ exit
exit
1 ~$

El número al comienzo de la cadena de solicitud indicará el nivel de shell.

Segunda variante, con niveles anidados de vim y shell ambos.

agregue estas líneas a la .bashrc

branch=$(pstree -ls $$)
vim_lvl=$(grep -o vim <<< "$branch" | wc -l)
sh_lvl=$(grep -o bash <<< "$branch" | wc -l)
PS1="v:${vim_lvl};s:$((sh_lvl - 1)):\w\$ "
export PS1

Resultado:

v:0;s:1:/etc$ bash
v:0;s:2:/etc$ bash
v:0;s:3:/etc$ vim
##### do ':sh' command in the vim, shell level is increasing by 1
v:1;s:4:/etc$ vim
##### do ':sh' command in the vim, shell level is increasing by 1
v:2;s:5:/etc$ bash
v:2;s:6:/etc$

v: 1 - nivel de profundidad vim
s: 3 - nivel de profundidad de caparazón

MiniMax
fuente
esto me dará el anidamiento bash. No me dará los nidos vim. :)
Pranay
@Pranay Verifique la nueva solución. Haciendo lo que quieres.
MiniMax
Sí, esta es una buena solución. Puedo agregar más conchas y funcionaría :).
Pranay
8

En la pregunta que mencionaste el análisis pstree. Aquí hay una forma relativamente simple:

bash-4.3$ pstree -Aals $$ | grep -E '^ *`-((|ba|da|k|c|tc|z)sh|vim?)( |$)'
                  `-bash
                      `-bash --posix
                          `-vi -y
                              `-dash
                                  `-vim testfile.txt
                                      `-tcsh
                                          `-csh
                                              `-sh -
                                                  `-zsh
                                                      `-bash --norc --verbose

Las pstreeopciones:

  • -A- Salida ASCII para un filtrado más fácil (en nuestro caso, cada comando está precedido por `-)
  • -a - muestre también los argumentos de los comandos, como efecto secundario cada comando se muestra en una línea separada y podemos filtrar fácilmente la salida usando grep
  • -l - no truncar líneas largas
  • -s- mostrar a los padres del proceso seleccionado
    (desafortunadamente no es compatible con versiones anteriores de pstree)
  • $$ - el proceso seleccionado - el PID del shell actual
pabouk
fuente
Sí, estaba haciendo esto más o menos. También tenía algo para contar "bash" y "vim", etc. Simplemente no deseaba mantenerlo. Tampoco es factible tener una gran cantidad de funcionalidades personalizadas cuando tiene que cambiar muchas VM y desarrollarlas a veces.
Pranay
3

Esto no responde estrictamente la pregunta, pero en muchos casos puede hacer que sea innecesario hacerlo:

Cuando inicies tu shell por primera vez, corre set -o ignoreeof. No lo pongas en tu ~/.bashrc.

Acostúmbrese a escribir Ctrl-D cuando piense que está en el nivel superior y quiera estar seguro.

Si estás no en la cáscara de alto nivel, Ctrl-D será la señal "fin de la entrada" en el shell actual y volverá a caer un nivel.

Si se encuentra en el nivel superior, recibirá un mensaje:

Use "logout" to leave the shell.

Lo uso todo el tiempo para sesiones SSH encadenadas, para que sea fácil volver a un nivel específico de la cadena SSH. Funciona para conchas anidadas también.

Comodín
fuente
1
Esto definitivamente ayuda y sí, eliminará muchas complicaciones :). Podría combinar esto con la respuesta aceptada :)). Condicionalmente configurado, por lo que es posible que no tenga que mirar mi mensaje todo el tiempo.
Pranay