Quante conchiglie sono profondo?


73

Problema : trova quante conchiglie sono profondo.

Dettagli : apro molto il guscio di VIM. Compilare, eseguire ed uscire. A volte dimentico e apro un altro filmato interno e poi ancora un altro guscio. :(

Voglio sapere quante shell sono profonde, forse le ho anche sempre sullo schermo della shell. (Posso gestire quella parte).

La mia soluzione : analizzare l'albero del processo e cercare vim e bash / zsh e capire la profondità del processo corrente al suo interno.

Esiste già qualcosa del genere? Non sono riuscito a trovare nulla.


27
La $SHLVLvariabile (gestita da più shell) è ciò che stai cercando?
Stéphane Chazelas,

1
Per chiarire, non sei veramente interessato a quante shell (nidificate direttamente), indicate da SHLVL, ma se la tua shell corrente è un discendente di vim?
Jeff Schaller

14
Questo sembra essere un po 'un problema XY - il mio flusso di lavoro è ^ Z per sfuggire dall'istanza vim nella shell genitore e fgtornare indietro, che non ha questo problema.
Doorknob,

2
@Doorknob, lo faccio anch'io. ma preferisco questo, perché poi dovrei continuare a controllare i "lavori". e ci possono essere molti in esecuzione a volte sulla mia macchina. ora aggiungi TMUX con l'equazione. Diventa complesso e traboccante. Se spawn shell all'interno di Vim, sarebbe meno di una dispersione. (Comunque finisco per fare il casino e quindi la domanda).
Pranay,

3
@Doorknob: Con tutto il rispetto, sembra rispondere alla domanda "Come guido dal punto A dal punto B?" Con il suggerimento "Non guidare; basta prendere un Uber. ”Se l'utente ha un flusso di lavoro che comporta la modifica di più file contemporaneamente, avere più vimprocessi paralleli arrestati potrebbe essere più confuso che avere una pila di processi nidificati. A proposito , preferisco avere più finestre , quindi posso facilmente saltare avanti e indietro rapidamente, ma non lo definirei un problema XY solo perché preferisco un diverso flusso di lavoro.
Scott,

Risposte:


45

Quando ho letto la tua domanda, il mio primo pensiero è stato $SHLVL. Poi ho visto che volevi contare i vimlivelli oltre ai livelli di shell. Un modo semplice per farlo è definire una funzione shell:

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

Ciò aumenterà automaticamente e silenziosamente SHLVL ogni volta che si digita un vimcomando. Dovrai farlo per ogni variante di vi/ vimche tu abbia mai usato; per esempio,

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

Il set esterno di parentesi crea una subshell, quindi la modifica manuale del valore di SHLVL non contamina l'ambiente di shell corrente (parent). Naturalmente la commandparola chiave è lì per impedire alle funzioni di chiamarsi (il che si tradurrebbe in un ciclo di ricorsione infinito). E ovviamente dovresti inserire queste definizioni nel tuo .bashrco in un altro file di inizializzazione della shell.


C'è una leggera inefficienza in quanto sopra. In alcune conchiglie (bash essendo uno), se dici

( cmd 1 ;  cmd 2 ;;  cmd n )

dove c'è un programma esterno eseguibile (cioè non un comando integrato), la shell mantiene un processo aggiuntivo in giro, solo per aspettare che finisca . Questo (discutibilmente) non è necessario; i vantaggi e gli svantaggi sono discutibili. Se non ti dispiace legare un po 'di memoria e uno slot di processo (e vedere un processo di shell in più di quello che ti serve quando fai un ), fai quanto sopra e passa alla sezione successiva. Idem se stai usando una shell che non tiene il processo extra in giro. Ma, se vuoi evitare il processo extra, una prima cosa da provare ècmdncmdnps

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

Il execcomando è lì per impedire che il processo di shell extra persista.

Ma c'è un gotcha. La gestione della shell SHLVLè in qualche modo intuitiva: quando la shell si avvia, controlla se SHLVLè impostata. Se non è impostato (o impostato su qualcosa di diverso da un numero), la shell lo imposta su 1. Se è impostato (su un numero), la shell aggiunge 1 ad esso.

Ma, secondo questa logica, se dici exec sh, SHLVLdovresti salire. Ma questo è indesiderabile, perché il tuo livello di shell reale non è aumentato. La shell gestisce questo sottraendo uno da SHLVL quando si esegue 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

Così

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

è un lavaggio; aumenta SHLVLsolo per ridurlo di nuovo. Potresti anche solo dire vim, senza il beneficio di una funzione.

Nota:
Secondo Stéphane Chazelas (che sa tutto) , alcune conchiglie sono abbastanza intelligenti da non farlo se si exectrovano in una subshell.

Per risolvere questo, lo faresti

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

Poi ho visto che volevi contare i vimlivelli indipendentemente dai livelli della shell. Bene, lo stesso trucco esatto funziona (bene, con una piccola modifica):

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

(e così via per vi, viewecc) Il exportè necessaria perché VILVLnon è definito come variabile di ambiente di default. Ma non ha bisogno di far parte della funzione; puoi semplicemente dire export VILVLcome comando separato (nel tuo .bashrc). E, come discusso sopra, se il processo di shell extra non è un problema per te, puoi fare command viminvece exec vime lasciare SHLVLda solo:

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

Preferenza personale:
potresti voler rinominare VILVLqualcosa di simile VIM_LEVEL. Quando guardo “ VILVL”, mi fanno male gli occhi; non sanno dire se si tratta di un errore ortografico di "vinile" o di un numero romano malformato.


Se stai usando una shell che non supporta SHLVL(es. Trattino), puoi implementarla tu stesso fintanto che la shell implementa un file di avvio. Fai qualcosa del genere

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

nel tuo .profileo nel file applicabile. (Probabilmente non dovresti usare il nome SHLVL, poiché ciò causerà il caos se dovessi iniziare a usare una shell che supporta SHLVL.)


Altre risposte hanno risolto il problema dell'incorporamento dei valori della variabile di ambiente nel prompt della shell, quindi non lo ripeterò, in particolare dici che sai già come farlo.


1
Sono un po 'perplesso che così tante risposte suggeriscano di eseguire un programma eseguibile esterno, come pso pstree, quando puoi farlo con i builtin della shell.
Scott,

Questa risposta è perfetta Ho indicato questa come soluzione (purtroppo non ha ancora molti voti).
Pranay,

Il tuo approccio è sorprendente e stai usando solo le primitive, il che significa che includerlo nel mio .profile / .shellrc non romperà nulla. Li tiro su qualsiasi macchina su cui lavoro.
Pranay,

1
Si noti che dashha un'espansione aritmetica. SHELL_LEVEL=$((SHELL_LEVEL + 1))dovrebbe essere sufficiente anche se $ SHELL_LEVEL era precedentemente non impostato o vuoto. E 'solo se si doveva essere portatile al Bourne shell che avresti bisogno di ricorrere a expr, ma poi si sarebbe anche necessario sostituire $(...)con `..`. SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Stéphane Chazelas,

2
@Pranay, è improbabile che sia un problema. Se un attaccante può iniettare qualsiasi ambiente arbitrario, allora cose come PATH / LD_PRELOAD sono scelte più ovvie, ma se le variabili non problematiche passano, come con sudo configurato senza reset_env (e si può forzare uno bashscript a leggere ~ / .bashrc di rendere stdin un socket per esempio), quindi questo può diventare un problema. Ci sono molti "se", ma qualcosa da tenere a mente (i dati non resi noti in un contesto aritmetico sono pericolosi)
Stéphane Chazelas,

37

Puoi contare quante volte hai bisogno di salire sull'albero del processo fino a trovare un leader della sessione. Come zshsu 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 (ma meno efficiente):

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"
)

Ciò darebbe 0 per la shell che è stata avviata dall'emulatore di terminale o getty e un'altra per ogni discendente.

Devi solo farlo una volta all'avvio. Ad esempio con:

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

nel tuo ~/.zshrco equivalente per averlo nel tuo prompt.

tcshe diverse altre shell ( zsh, ksh93, fishe bashalmeno) mantengono una $SHLVLvariabile che si incrementano all'avvio (e decremento prima di eseguire un altro comando con exec(a meno che execsia, se non sono buggy (ma molti sono in una subshell))). Ciò tiene traccia solo della quantità di annidamento della shell , non dell'annidamento del processo. Inoltre, il livello 0 non è garantito per essere il leader della sessione.


Sì .. questo o simile. Non volevo scriverlo da solo, e non era mia intenzione che qualcuno lo scrivesse per me. :(. Speravo in qualche funzione in vim o shell o in qualche plugin che è regolarmente mantenuto. Ho cercato ma non ho trovato qualcosa.
Pranay,

31

Usa echo $SHLVL. Usa il principio KISS . A seconda della complessità del programma, questo potrebbe essere sufficiente.


2
Funziona per bash, ma non per dash.
agc,

SHLVL non mi aiuta. Lo sapevo, ed è emerso anche nella ricerca quando ho cercato. :) Ci sono maggiori dettagli nella domanda.
Pranay,

@Pranay Sei sicuro che vim stesso non fornisca queste informazioni?
user2497

@ user2497, sono un po '. Questa è la premessa della domanda. Ho cercato dappertutto, ero a conoscenza anche di SHLVL. Volevo -> a) essere certo che non esiste nulla del genere. b) farlo con il minor numero di dipendenze / manutenzione.
Pranay,

16

Una potenziale soluzione è esaminare l'output di pstree. Quando eseguito all'interno di una shell che è stata generata dall'interno vi, la parte dell'albero dell'albero che elenca pstreedovrebbe mostrarti quanto sei profondo. Per esempio:

$ pstree <my-user-ID>
...
       ├─gnome-terminal-─┬─bash───vi───sh───vi───sh───pstree
...

Sì, è quello che ho suggerito come soluzione (nella domanda). Non voglio analizzare il pstree però :(. Questo è buono per leggerlo manualmente, stavo pensando di scrivere un programma per farlo per me e farmelo sapere. Non sono molto propenso a scrivere un parser se un plugin / lo strumento lo fa già :).
Pranay,

11

Prima variante: solo profondità della shell.

Soluzione semplice per bash: aggiungere alle .bashrcdue righe successive (o modificare il PS1valore corrente ):

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

Risultato:

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

Il numero all'inizio della stringa del prompt indicherà il livello della shell.

Seconda variante, con entrambi i livelli di vim e shell nidificati.

aggiungi queste righe al file .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

Risultato:

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 - livello di profondità vim
s: 3 - livello di profondità della shell


questo mi darà la nidificazione bash. Non mi darà i nidi vim. :)
Pranay,

@Pranay Controlla la nuova soluzione. Fa quello che vuoi.
MiniMax,

Sì, questa è una buona soluzione. Posso aggiungere più shell e funzionerebbe :).
Pranay,

8

Nella domanda di cui hai parlato dell'analisi pstree. Ecco un modo relativamente semplice:

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

Le pstreeopzioni:

  • -A- Uscita ASCII per un filtraggio più semplice (nel nostro caso ogni comando è preceduto da `-)
  • -a - mostra anche gli argomenti dei comandi, come effetto collaterale ogni comando viene mostrato su una riga separata e possiamo facilmente filtrare l'output usando grep
  • -l - non troncare le linee lunghe
  • -s- mostra i genitori del processo selezionato
    (purtroppo non supportato nelle vecchie versioni di pstree)
  • $$ - il processo selezionato - il PID della shell corrente

Sì, lo stavo facendo praticamente. Avevo anche qualcosa per contare "bash" e "vim" ecc. Non volevo solo mantenerlo. Inoltre, non è possibile avere molte funzionalità personalizzate quando è necessario cambiare molte macchine virtuali e svilupparle su di esse a volte.
Pranay,

3

Questo non risponde rigorosamente alla domanda, ma in molti casi potrebbe non essere necessario farlo:

Quando avvii la shell per la prima volta, corri set -o ignoreeof. Non metterlo nel tuo ~/.bashrc.

Prendi l'abitudine di digitare Ctrl-D quando pensi di essere nella shell di livello superiore e vuoi esserne sicuro.

Se sei non nella shell di alto livello, Ctrl-D segnalerà "fine dell'input" per la shell corrente e si lascerà cadere indietro di un livello.

Se siete nella shell di livello superiore, si otterrà un messaggio:

Use "logout" to leave the shell.

Lo uso sempre per sessioni SSH concatenate, per facilitare il ritorno a un livello specifico della catena SSH. Funziona anche con shell nidificate.


1
Questo sicuramente aiuta e sì, rimuoverà molte complicazioni :). Potrei semplicemente combinare questo con la risposta accettata :)). Impostato in modo condizionale, quindi potrei non dover guardare il mio prompt per tutto il tempo.
Pranay,
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.