Ile jestem głębokich pocisków?


73

Problem : znajdź głębokość pocisków.

Szczegóły : Często otwieram powłokę z vima. Zbuduj, uruchom i zakończ. Czasami zapominam i otwieram kolejną vimę, a potem kolejną powłokę. :(

Chcę wiedzieć, ile mam głębokich pocisków, być może nawet mam je przez cały czas na ekranie. (Mogę zarządzać tą częścią).

Moje rozwiązanie : parsuj drzewo procesów i poszukaj vim i bash / zsh i oblicz głębokość bieżącego procesu.

Czy coś takiego już istnieje? Nic nie mogłem znaleźć.


27
Czy $SHLVLzmienna (obsługiwana przez kilka powłok) jest tym, czego szukasz?
Stéphane Chazelas

1
Aby wyjaśnić, tak naprawdę nie interesuje Cię, ile (bezpośrednio zagnieżdżonych) powłok, wskazanych przez SHLVL, ale czy twoja obecna powłoka jest potomkiem vima?
Jeff Schaller

14
To wydaje się być trochę problemem XY - mój przepływ pracy to ^ Z, aby uciec z instancji vim do powłoki nadrzędnej i fgwrócić, która nie ma tego problemu.
Klamka

2
@Doorknob, ja też to robię. ale wolę ten, ponieważ wtedy musiałbym sprawdzać „zadania”. i na moim komputerze może być wiele uruchomionych czasami. teraz dodaj TMUX z równaniem. Staje się złożony i przepełniony. Jeśli odrodzę skorupę w vimie, byłoby to mniej rozproszenia. (Jednak ostatecznie robię bałagan i stąd pytanie).
Pranay

3
@Doorknob: Z całym szacunkiem wydaje się to odpowiadać na pytanie: „Jak prowadzić z punktu A z punktu B?” Z sugestią: „Nie prowadź; po prostu weź Ubera. ”Jeśli użytkownik ma przepływ pracy obejmujący edycję wielu plików jednocześnie, to posiadanie wielu równolegle zatrzymanych vimzadań może być bardziej mylące niż stos stosu procesów zagnieżdżonych. Nawiasem mówiąc, wolę mieć wiele okien , więc mogę łatwo skakać szybko tam iz powrotem, ale nie nazwałbym tego problemem XY tylko dlatego, że wolę inny przepływ pracy.
Scott

Odpowiedzi:


45

Kiedy przeczytałem twoje pytanie, moją pierwszą myślą było $SHLVL. Potem zobaczyłem, że chcesz liczyć vimpoziomy oprócz poziomów powłoki. Prostym sposobem na to jest zdefiniowanie funkcji powłoki:

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

To będzie automatycznie i cicho zwiększać za SHLVL każdym razem, gdy wpiszesz vimpolecenie. Musisz to zrobić dla każdego wariantu vi/ vim, którego kiedykolwiek używałeś; na przykład,

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

Zewnętrzny zestaw nawiasów tworzy podpowłokę, więc ręczna zmiana wartości SHLVL nie zanieczyszcza bieżącego (macierzystego) środowiska powłoki. Oczywiście commandsłowo kluczowe ma zapobiegać wywoływaniu się funkcji (co spowodowałoby nieskończoną pętlę rekurencji). I oczywiście powinieneś umieścić te definicje w swoim .bashrclub innym pliku inicjującym powłokę.


Powyżej jest niewielka nieefektywność. W niektórych muszlach (bash to jeden), jeśli tak mówisz

( cmd 1 ;  cmd 2 ;;  cmd n )

gdzie jest zewnętrzny program wykonywalny (tj. nie wbudowane polecenie), powłoka utrzymuje dodatkowy proces leżący w pobliżu, tylko po to, aby czekać na zakończenie. To (prawdopodobnie) nie jest konieczne; zalety i wady są dyskusyjne. Jeśli nie masz nic przeciwko związaniu odrobiny pamięci i gniazda procesu (i zobaczeniu o jeden proces powłoki więcej niż potrzebujesz, gdy wykonujesz a ), to wykonaj powyższe czynności i przejdź do następnej sekcji. To samo, jeśli używasz powłoki, która nie utrzymuje dodatkowego procesu. Ale jeśli chcesz uniknąć dodatkowego procesu, pierwszą rzeczą do wypróbowania jestcmdncmdnps

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

Jest execto polecenie, aby zapobiec dalszemu procesowi powłoki.

Ale jest gotcha. Obsługa powłoki SHLVLjest nieco intuicyjna: gdy powłoka się uruchamia, sprawdza, czy SHLVLjest ustawiona. Jeśli nie jest ustawiony (lub ustawiony na coś innego niż liczba), powłoka ustawia go na 1. Jeśli jest ustawiony (na liczbę), powłoka dodaje do niego 1.

Ale według tej logiki, jeśli tak mówisz exec sh, SHLVLpowinieneś pójść w górę. Ale to niepożądane, ponieważ twój rzeczywisty poziom pocisków nie wzrósł. Powłoka radzi sobie z tym, odejmując jedną z następujących SHLVL czynności 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

Więc

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

jest praniem; zwiększa się SHLVLtylko w celu ponownego zmniejszenia. Równie dobrze możesz powiedzieć vim, bez korzystania z funkcji.

Uwaga:
Według Stéphane'a Chazelasa (który wie wszystko) , niektóre powłoki są wystarczająco inteligentne, aby tego nie robić, jeśli execsą w podpowłoce.

Aby to naprawić, zrobiłbyś

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

Potem zobaczyłem, że chcesz liczyć vimpoziomy niezależnie od poziomów powłoki. Cóż, działa dokładnie ta sama sztuczka (z niewielką modyfikacją):

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

(i tak dalej vi, viewitp) Jak exportto konieczne, ponieważ VILVLnie jest zdefiniowany jako zmiennej środowiskowej domyślnie. Ale to nie musi być częścią funkcji; możesz po prostu wypowiedzieć export VILVLjako osobne polecenie (w swoim .bashrc). Jak wspomniano powyżej, jeśli proces dodatkowej powłoki nie stanowi dla ciebie problemu, możesz command vimzamiast tego zrobić exec vimi zostawić w SHLVLspokoju:

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

Preferencje osobiste:
możesz chcieć zmienić nazwę VILVLna coś podobnego VIM_LEVEL. Kiedy patrzę na „ VILVL”, bolą mnie oczy; nie potrafią powiedzieć, czy jest to błędna pisownia „winylu”, czy zniekształcona rzymska cyfra.


Jeśli używasz powłoki, która nie obsługuje SHLVL(np. Myślnik), możesz ją zaimplementować samodzielnie, o ile powłoka implementuje plik startowy. Po prostu zrób coś takiego

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

w twoim .profilelub odpowiednim pliku. (Prawdopodobnie nie powinieneś używać nazwy SHLVL, ponieważ spowoduje to chaos, jeśli zaczniesz używać powłoki, która obsługuje SHLVL).


Inne odpowiedzi dotyczyły problemu osadzania wartości zmiennych środowiskowych w zachęcie powłoki, więc nie powtórzę tego, zwłaszcza, że ​​mówisz, że już wiesz, jak to zrobić.


1
Jestem nieco zdziwiony, że tak wiele odpowiedzi sugeruje wykonanie zewnętrznego programu wykonywalnego, takiego jak pslub pstree, kiedy można to zrobić za pomocą wbudowanych powłok.
Scott

Ta odpowiedź jest idealna. Zaznaczyłem to jako rozwiązanie (niestety nie ma jeszcze tylu głosów).
Pranay

Twoje podejście jest niesamowite i używasz tylko prymitywów, co oznacza, że ​​włączenie tego do mojego .profile / .shellrc niczego nie zepsuje. Ciągnę je na dowolnej maszynie, na której pracuję.
Pranay

1
Zauważ, że dashma rozszerzenie arytmetyczne. SHELL_LEVEL=$((SHELL_LEVEL + 1))powinno wystarczyć, nawet jeśli $ SHELL_LEVEL był wcześniej rozbrojony lub pusty. To tylko jeśli miał być przenośne do powłoki Bourne, że trzeba by uciekać się do expr, ale wtedy trzeba także wymienić $(...)się `..`. SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Stéphane Chazelas

2
@Pranay, to raczej nie będzie problem. Jeśli atakujący może wstrzyknąć dowolny dowolny env var, a potem takie rzeczy jak ścieżka / LD_PRELOAD są bardziej oczywiste wybory, ale jeśli zmienne niż problematyczne przejść, jak z sudo skonfigurowany bez reset_env (i można zmusić bashskrypt do odczytania przez ~ / .bashrc na przykład stdin gniazdo), to może stać się problemem. To dużo „jeśli”, ale coś, o czym warto pamiętać (dane niezanalizowane w kontekście arytmetycznym są niebezpieczne)
Stéphane Chazelas

37

Możesz policzyć tyle czasu, ile potrzebujesz, aby przejść do drzewa procesów, aż znajdziesz lidera sesji. Podobnie jak w przypadku zshsystemu 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
}

Lub POSIXly (ale mniej wydajny):

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

To dałoby 0 dla powłoki, która została uruchomiona przez emulator terminala lub getty, i jedną więcej dla każdego potomka.

Musisz to zrobić tylko raz podczas uruchamiania. Na przykład z:

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

w swoim ~/.zshrclub równoważnym, aby mieć go w swoim podpowiedzi.

tcshi kilka innych powłok ( zsh, ksh93, fishi bashco najmniej) utrzymywać $SHLVLzmienną których przyrost na starcie (i ubytku przed uruchomieniem kolejnego polecenia z exec(chyba, że execjest w podpowłoce, jeśli nie są one buggy (ale wiele z nich))). Śledzi to tylko ilość zagnieżdżenia powłoki , a nie proces zagnieżdżania. Również poziom 0 nie jest gwarantowany jako lider sesji.


Tak .. to lub podobne. Nie chciałem tego pisać, i nie było moim zamiarem, aby ktoś napisał to dla mnie. :(. Miałem nadzieję na jakąś funkcję w vimie lub powłoce lub jakąś wtyczkę, która jest regularnie utrzymywana. Szukałem, ale czegoś nie znalazłem.
Pranay

31

Użyj echo $SHLVL. Użyj zasady KISS . W zależności od złożoności programu może to wystarczyć.


2
Działa dla bash, ale nie dla dash.
agc

SHLVL mi nie pomaga. Wiedziałem o tym, a także przy wyszukiwaniu pojawiło się. :) W pytaniu jest więcej szczegółów.
Pranay

@Pranay Czy jesteś pewien, że sam vim nie udostępnia tych informacji?
user2497

@ user2497, jestem nieco. To jest przesłanka pytania. Szukałem wszędzie, wiedziałem też o SHLVL. Chciałem -> a) mieć pewność, że nie ma czegoś takiego. b) rób to przy jak najmniejszej liczbie zależności / konserwacji.
Pranay

16

Jednym potencjalnym rozwiązaniem jest przyjrzenie się wynikowi pstree. Po uruchomieniu w powłoce, która została spawnowana od wewnątrz vi, część drzewa drzewa, która zawiera listy, pstreepowinna pokazać ci, jak głęboko jesteś. Na przykład:

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

Tak, to właśnie zaproponowałem jako moje rozwiązanie (w pytaniu). Nie chcę parsować pstree :(. To jest dobre do ręcznego czytania, myślałem o napisaniu programu, który by to dla mnie zrobił i dał mi znać. Nie jestem zbyt skłonny do napisania parsera, jeśli wtyczka / narzędzie już to robi :).
Pranay

11

Pierwszy wariant - tylko głębokość pocisku.

Proste rozwiązanie dla bash: dodaj do .bashrcnastępnych dwóch wierszy (lub zmień aktualną PS1wartość):

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

Wynik:

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

Liczba na początku łańcucha zachęty będzie oznaczać poziom powłoki.

Drugi wariant, zarówno z zagnieżdżonymi vimami, jak i poziomami powłoki.

dodaj te linie do .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

Wynik:

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 - poziom głębokości vim
s: 3 - poziom głębokości pocisku


to da mi zagnieżdżenie. Nie da mi gniazd vimów. :)
Pranay

@Pranay Sprawdź nowe rozwiązanie. Robi to, co chcesz.
MiniMax

Tak, to dobre rozwiązanie. Mogę dodać więcej powłok i to by działało :).
Pranay

8

W pytaniu, o którym wspominałeś, parsowaniu pstree. Oto stosunkowo prosty sposób:

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

Te pstreeopcje:

  • -A- Wyjście ASCII dla łatwiejszego filtrowania (w naszym przypadku każde polecenie poprzedza `-)
  • -a - pokaż również argumenty poleceń, jako efekt uboczny każde polecenie jest wyświetlane w osobnej linii i możemy łatwo filtrować dane wyjściowe za pomocą grep
  • -l - nie obcinaj długich linii
  • -s- pokaż rodziców wybranego procesu
    (niestety nie obsługiwany w starych wersjach pstree)
  • $$ - wybrany proces - PID bieżącej powłoki

Tak, właściwie to robiłem. Miałem też coś do policzenia „bash” i „vim” itd. Po prostu nie chciałem tego utrzymywać. Nie jest również możliwe posiadanie dużej liczby niestandardowych funkcji, gdy trzeba przełączyć wiele maszyn wirtualnych i czasami je rozwijać.
Pranay

3

To nie jest ścisła odpowiedź na pytanie, ale w wielu przypadkach może to powodować, że nie jest to konieczne:

Po pierwszym uruchomieniu powłoki uruchom set -o ignoreeof. Nie wkładaj tego do swojego ~/.bashrc.

Przyzwyczaj do pisania Ctrl-D, gdy myślisz, że jesteś w powłoce najwyższego poziomu i chcesz mieć pewność.

Jeśli jesteś nie w górę powłoki poziomie, Ctrl-D zasygnalizuje „koniec wejścia” do bieżącej powłoki i będzie spadać o jeden poziom.

Jeśli w górnej skorupy poziomu, dostaniesz wiadomość:

Use "logout" to leave the shell.

Cały czas używam tego do połączonych sesji SSH, aby ułatwić powrót do określonego poziomu łańcucha SSH. Działa również w przypadku zagnieżdżonych muszli.


1
To zdecydowanie pomaga i tak, usunie wiele komplikacji :). Mogę po prostu połączyć to z zaakceptowaną odpowiedzią :)). Ustawiony warunkowo, więc może nie będę musiał cały czas patrzeć na monit.
Pranay
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.