Wie viele Muscheln bin ich tief?


73

Problem : Finde heraus, wie viele Muscheln tief ich bin.

Details : Ich öffne die Shell sehr oft von vim. Bauen und ausführen und beenden. Manchmal vergesse ich und öffne ein anderes Vim und dann noch eine Muschel. :(

Ich möchte wissen, wie viele Muscheln tief ich bin, vielleicht habe ich sie sogar immer auf meinem Muschelschirm. (Ich kann diesen Teil verwalten).

Meine Lösung : Analysieren Sie den Prozessbaum, suchen Sie nach vim und bash / zsh und ermitteln Sie die Tiefe des aktuellen Prozesses.

Gibt es so etwas schon? Ich konnte nichts finden.


27
Ist die $SHLVLVariable (die von mehreren Shells verwaltet wird) das, wonach Sie suchen?
Stéphane Chazelas

1
Zur Verdeutlichung interessiert Sie nicht wirklich, wie viele (direkt verschachtelte) Shells SHLVL angibt, aber ob Ihre aktuelle Shell ein Nachkomme von vim ist?
Jeff Schaller

14
Dies scheint ein kleines XY-Problem zu sein. Mein Workflow besteht darin, aus der vim-Instanz in die übergeordnete Shell zu flüchten und fgzurückzukehren, was dieses Problem nicht hat.
Türknauf

2
@Doorknob, das mache ich auch. aber ich bevorzuge diesen, weil ich dann immer wieder "jobs" prüfen müsste. und es können manchmal viele auf meinem Rechner laufen. Fügen Sie nun TMUX mit der Gleichung hinzu. Es wird komplex und überfüllt. Wenn ich Shell in vim spawne, wäre das weniger eine Streuung. (Allerdings mache ich das Chaos und damit die Frage).
28.

3
@Doorknob: Bei allem Respekt scheint dies die Beantwortung der Frage „Wie fahre ich von Punkt A von Punkt B aus?“ Mit dem Vorschlag „Nicht fahren; Nehmen Sie einfach einen Uber. “Wenn der Benutzer über einen Workflow verfügt, in dem mehrere Dateien gleichzeitig bearbeitet werden, ist es vimmöglicherweise verwirrender , mehrere parallel gestoppte Jobs zu haben, als einen Stapel verschachtelter Prozesse. Im Übrigen bevorzuge ich mehrere Fenster , damit ich schnell hin und her springen kann, aber ich würde dies nicht als XY-Problem bezeichnen, nur weil ich einen anderen Workflow bevorzuge.
Scott

Antworten:


45

Als ich Ihre Frage las, war mein erster Gedanke $SHLVL. Dann sah ich, dass Sie vimEbenen zusätzlich zu Shell-Ebenen zählen wollten . Eine einfache Möglichkeit, dies zu tun, besteht darin, eine Shell-Funktion zu definieren:

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

Dies wird bei SHLVL jeder Eingabe eines vimBefehls automatisch und unbeaufsichtigt inkrementiert . Sie müssen dies für jede Variante von vi/ tun vim, die Sie jemals verwenden. z.B,

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

Der äußere Satz von Klammern erstellt eine Subshell, sodass die manuelle Änderung des Werts von SHLVL die aktuelle (übergeordnete) Shell-Umgebung nicht verschmutzt. Das commandSchlüsselwort soll natürlich verhindern, dass sich die Funktionen selbst aufrufen (was zu einer unendlichen Rekursionsschleife führen würde). Und natürlich sollten Sie diese Definitionen in Ihre .bashrcoder eine andere Shell-Initialisierungsdatei einfügen.


Es gibt eine leichte Ineffizienz in der obigen. In einigen Muscheln (Bash ist eins), wenn Sie sagen

( cmd 1 ;  cmd 2 ;;  cmd n )

Wenn es sich um ein externes ausführbares Programm handelt (dh nicht um einen eingebauten Befehl), hält die Shell einen zusätzlichen Prozess herum, nur um auf das Beenden zu warten . Dies ist (wohl) nicht notwendig; Die Vor- und Nachteile sind umstritten. Wenn es Ihnen nichts ausmacht, ein wenig Speicher und einen Prozess-Slot zu belegen (und einen Shell-Prozess mehr zu sehen, als Sie benötigen, wenn Sie a ausführen ), gehen Sie wie oben beschrieben vor und fahren Sie mit dem nächsten Abschnitt fort. Das Gleiche gilt, wenn Sie eine Shell verwenden, bei der der zusätzliche Prozess nicht herumliegt. Wenn Sie jedoch den zusätzlichen Prozess vermeiden möchten, sollten Sie zunächst versuchen, dies zu tuncmdncmdnps

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

Der execBefehl soll verhindern, dass der zusätzliche Shell-Prozess nachlässt.

Aber es gibt ein GOTCHA. Die Bedienung der Shell SHLVList etwas intuitiv: Beim Start der Shell wird geprüft, ob SHLVLgesetzt ist. Wenn es nicht gesetzt ist (oder auf etwas anderes als eine Zahl gesetzt ist), setzt die Shell es auf 1. Wenn es gesetzt ist (auf eine Zahl), fügt die Shell 1 hinzu.

Aber von dieser Logik, wenn Sie sagen exec sh, Ihr SHLVLsollte steigen. Dies ist jedoch unerwünscht, da sich Ihr tatsächlicher Shell-Level nicht erhöht hat. Die Muschelgriffe dies durch eine Subtraktion von SHLVL , wenn Sie tun ein 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

Damit

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

ist eine Wäsche; es wird SHLVLnur inkrementiert , um es erneut zu dekrementieren. Sie können auch einfach sagen vim, ohne eine Funktion zu nutzen.

Hinweis:
Laut Stéphane Chazelas (der alles weiß) sind einige Muscheln klug genug, dies nicht zu tun, wenn sie execsich in einer Unterschale befinden.

Um dies zu beheben, würden Sie tun

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

Dann habe ich gesehen, dass Sie vimEbenen unabhängig von Shell-Ebenen zählen möchten. Nun, genau der gleiche Trick funktioniert (nun, mit einer kleinen Modifikation):

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

(und so weiter für vi, viewusw.) Das exportist notwendig, da VILVLes standardmäßig nicht als Umgebungsvariable definiert ist. Es muss jedoch nicht Teil der Funktion sein. Sie können einfach export VILVLals separates Kommando (in Ihrem .bashrc) sagen . Und, wie oben beschrieben, wenn der zusätzliche Shell - Prozess für Sie kein Problem ist, können Sie tun , command vimstatt exec vim, und lassen Sie SHLVLallein:

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

Persönliche Präferenz:
Möglicherweise möchten Sie in VILVLso etwas wie umbenennen VIM_LEVEL. Wenn ich auf " VILVL" schaue , schmerzen meine Augen; Sie können nicht sagen, ob es sich um eine falsche Schreibweise von „Vinyl“ oder eine falsch geformte römische Ziffer handelt.


Wenn Sie eine Shell verwenden, die nicht unterstützt wird SHLVL(z. B. Bindestrich), können Sie sie selbst implementieren, solange die Shell eine Startdatei implementiert. Mach einfach so etwas wie

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

in Ihrer .profileoder einer entsprechenden Datei. (Sie sollten den Namen wahrscheinlich nicht verwenden SHLVL, da dies zu Chaos führen kann, wenn Sie jemals eine unterstützte Shell verwenden SHLVL.)


Andere Antworten haben das Problem der Einbettung von Umgebungsvariablenwerten in Ihre Shell-Eingabeaufforderung angesprochen, daher werde ich das nicht wiederholen, besonders wenn Sie sagen, dass Sie bereits wissen, wie es geht.


1
Ich bin etwas verwirrt, dass so viele Antworten darauf hindeuten, ein externes ausführbares Programm auszuführen, wie psoder pstree, wenn Sie dies mit Shell-Builtins tun können.
Scott

Diese Antwort ist perfekt. Ich habe dies als die Lösung markiert (leider hat es noch nicht so viele Stimmen).
28.

Ihr Ansatz ist erstaunlich und Sie verwenden nur die Grundelemente, was bedeutet, dass die Aufnahme in mein .profile / .shellrc nichts kaputt macht. Ich ziehe die auf jeder Maschine, an der ich arbeite.
28.

1
Beachten Sie, dass dasharithmetische Erweiterung hat. SHELL_LEVEL=$((SHELL_LEVEL + 1))sollte auch dann ausreichen, wenn $ SHELL_LEVEL zuvor nicht gesetzt oder leer war. Es ist nur , wenn Sie an der Bourne - Shell tragbar sein musste , die Sie zurückgreifen müssen würde expr, aber dann würden Sie auch ersetzen müssen $(...)mit `..`. SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Stéphane Chazelas

2
@Pranay, es ist unwahrscheinlich, dass es ein Problem ist. Wenn ein Angreifer eine beliebige env-Variable einschleusen kann, sind Dinge wie PATH / LD_PRELOAD die naheliegendste Wahl, aber wenn unproblematische Variablen durchkommen, wie bei sudo, das ohne reset_env konfiguriert wurde (und man ein bashSkript dazu zwingen kann, ~ / .bashrc von zu lesen) Wenn Sie beispielsweise stdin zu einer Steckdose machen, kann dies zu einem Problem werden. Das ist eine Menge "Wenn", aber etwas, das man im Hinterkopf behalten sollte (nicht bereinigte Daten im arithmetischen Kontext sind gefährlich)
Stéphane Chazelas

37

Sie können so viele Zeit in Anspruch nehmen, bis Sie einen Sitzungsleiter gefunden haben. Wie bei zshLinux:

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
}

Oder POSIXly (aber weniger effizient):

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

Das würde 0 für die Shell ergeben, die von Ihrem Terminalemulator oder getty gestartet wurde, und eine weitere für jeden Nachkommen.

Sie müssen dies nur einmal beim Start tun. Zum Beispiel mit:

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

in Ihrem ~/.zshrcoder einem gleichwertigen Format, um es in Ihrer Aufforderung zu haben.

tcshund mehrere andere Muscheln ( zsh, ksh93, fishund bashzumindest) halten eine $SHLVLVariable , die sie beim Start erhöhen (und Abnahme vor einem anderen Befehl ausgeführt werden mit exec(es sei denn , dass execin einer Subshell wenn sie nicht Buggy (aber viele sind))). Dass nur die Menge von Spuren Schale Verschachtelung nicht aber verarbeiten Verschachtelung. Es wird auch nicht garantiert, dass Stufe 0 der Sitzungsleiter ist.


Ja .. das oder ähnlich. Ich wollte das nicht selbst schreiben, und es war nicht meine Absicht, dass jemand das für mich schreibt. :(. Ich hoffte auf ein Feature in Vim oder Shell oder ein Plugin, das regelmäßig gewartet wird. Ich habe gesucht, aber nichts gefunden.
Pranay

31

Verwenden Sie echo $SHLVL. Verwenden Sie das KISS-Prinzip . Abhängig von der Komplexität Ihres Programms kann dies ausreichen.


2
Funktioniert für bash, aber nicht für dash.
Agc

SHLVL hilft mir nicht. Ich wusste davon, und es tauchte auch bei der Suche auf, als ich suchte. :) Es gibt weitere Details in der Frage.
29.

@Pranay Sind Sie sicher, dass vim selbst diese Informationen nicht bereitstellt?
user2497

@ user2497, ich bin etwas. Dies ist die Voraussetzung der Frage. Ich habe überall gesucht und war mir auch der SHLVL bewusst. Ich wollte -> a) sicher sein, dass es so etwas nicht gibt. b) Machen Sie es mit der geringsten Menge an Abhängigkeiten / Wartung.
29.

16

Eine mögliche Lösung besteht darin, den Output von zu betrachten pstree. Wenn Sie in einer Shell laufen vi, die von innen erzeugt wurde , sollte der Teil des Baumbaums, der aufgelistet wird pstree, Ihnen zeigen, wie tief Sie sind. Zum Beispiel:

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

Ja, das ist, was ich als meine Lösung vorgeschlagen habe (in der Frage). Ich möchte den pstree aber nicht parsen :(. Dies ist gut zum manuellen Lesen, ich dachte darüber nach, ein Programm zu schreiben, um es für mich zu tun und lass es mich wissen. Ich bin nicht sehr geneigt, einen Parser zu schreiben, wenn ein Plugin / Werkzeug macht es schon :).
Pranay

11

Erste Variante - nur Schalentiefe.

Einfache Lösung für bash: Addiere zu den .bashrcnächsten zwei Zeilen (oder ändere deinen aktuellen PS1Wert):

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

Ergebnis:

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

Die Zahl am Anfang der Eingabeaufforderungszeichenfolge gibt die Shell-Ebene an.

Zweite Variante mit verschachtelten Vim- und Shell-Ebenen.

füge diese Zeilen dem hinzu .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

Ergebnis:

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 - Tiefenlevel vim
s: 3 - Tiefenlevel shell


das wird mir die Bash-Verschachtelung geben. Es wird mir nicht die Vim-Nester geben. :)
Pranay

@Pranay Überprüfen Sie die neue Lösung. Es macht was du willst.
MiniMax

Ja, das ist eine gute Lösung. Ich kann mehr Muscheln hinzufügen und es würde funktionieren :).
28.

8

In der Frage, die Sie erwähnt haben, analysieren von pstree. Hier ist ein relativ einfacher Weg:

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

Die pstreeOptionen:

  • -A- ASCII-Ausgabe zur einfacheren Filterung (in unserem Fall wird jedem Befehl ein vorangestellt `-)
  • -a - zeige auch Befehlsargumente an, als Nebeneffekt wird jeder Befehl in einer eigenen Zeile angezeigt und wir können die Ausgabe einfach mit filtern grep
  • -l - Schneiden Sie keine langen Zeilen ab
  • -s- Eltern des ausgewählten Prozesses
    anzeigen (wird in alten Versionen von leider nicht unterstützt pstree)
  • $$ - der ausgewählte Prozess - die PID der aktuellen Shell

Ja, ich habe das ziemlich oft gemacht. Ich hatte auch etwas zu zählen "Bash" und "Vim" usw. Ich wollte es einfach nicht beibehalten. Es ist auch nicht machbar, viele benutzerdefinierte Funktionen zu haben, wenn Sie viele VMs umstellen und manchmal darauf entwickeln müssen.
28.

3

Dies beantwortet die Frage nicht unbedingt , macht sie jedoch in vielen Fällen unnötig:

Wenn Sie Ihre Shell zum ersten Mal starten, führen Sie sie aus set -o ignoreeof. Steck es nicht in deine ~/.bashrc.

Machen Sie es sich zur Gewohnheit, Strg-D einzugeben, wenn Sie glauben, dass Sie sich in der obersten Ebene der Shell befinden und sicher sein möchten.

Wenn Sie sich nicht in der Shell der obersten Ebene befinden, signalisiert Strg-D der aktuellen Shell das "Ende der Eingabe" und Sie werden eine Ebene zurückgesetzt.

Wenn Sie sich in der obersten Ebene der Shell befinden, erhalten Sie eine Nachricht:

Use "logout" to leave the shell.

Ich benutze dies die ganze Zeit für verkettete SSH-Sitzungen, um es einfach zu machen, auf eine bestimmte Ebene der SSH-Kette zurückzukehren. Es funktioniert genauso gut für verschachtelte Shells.


1
Das hilft auf jeden Fall und ja, es wird viele Komplikationen beseitigen :). Ich könnte dies einfach mit der akzeptierten Antwort kombinieren :)). Bedingt festgelegt, sodass ich möglicherweise nicht die ganze Zeit auf meine Eingabeaufforderung achten muss.
Pranay
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.