Скільки я глибоких снарядів?


73

Проблема : Знайди скільки глибоких снарядів у мене.

Деталі : я дуже відкриваю оболонку від vim. Побудуйте та запустіть та вийдіть. Іноді я забуваю і відкриваю інший vim всередині, а потім ще одну оболонку. :(

Хочеться знати, скільки в мене глибоких снарядів, можливо, навіть є це на моєму екрані оболонок. (Я можу керувати цією частиною).

Моє рішення : Проаналізуйте дерево процесу та знайдіть vim та bash / zsh та визначте глибину поточного процесу в ньому.

Чи щось подібне вже існує? Я нічого не міг знайти.


27
Чи $SHLVLпотрібна змінна (підтримується кількома оболонками)?
Стефан Шазелас

1
Для уточнення вас не дуже цікавить, скільки (безпосередньо вкладених) снарядів, зазначених SHLVL, але чи ваша поточна оболонка є нащадком vim?
Jeff Schaller

14
Це здається чимось проблемою XY - мій робочий процес - ^ Z, щоб вийти з екземпляра vim в батьківську оболонку і fgповернутися назад, у якого немає цієї проблеми.
Doorknob

2
@Doorknob, я теж це роблю. але я віддаю перевагу цьому, бо тоді мені доведеться постійно перевіряти "робочі місця". і На моїй машині часом може працювати багато. тепер додайте TMUX з рівнянням. Він стає складним і переповненим. Якби я породив оболонку всередині vim, це було б менше розкиду. (Однак я закінчую безлад і звідси питання).
Праней

3
@Doorknob: З усією повагою, схоже, відповідаючи на запитання "Як я їхати з точки А з точки В?" Із пропозицією: "Не їдьте; просто візьміть Uber. "Якщо у користувача є робочий процес, який включає редагування декількох файлів одночасно, то наявність декількох паралельних зупинених vimзавдань може бути більш заплутаною, ніж наявність стека вкладених процесів. Між іншим , я вважаю за краще мати декілька вікон , тому я можу легко стрибати вперед і назад швидко, але я б не називав це проблемою XY лише тому, що я віддаю перевагу іншому робочому процесу.
Скотт

Відповіді:


45

Коли я читав ваше запитання, моя перша думка була $SHLVL. Потім я побачив, що ви хочете порахувати vimрівні, крім рівнів оболонки. Простий спосіб зробити це - визначити функцію оболонки:

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

Це автоматично та беззвучно збільшуватиметься SHLVL кожного разу при введенні vimкоманди. Вам потрібно буде зробити це для кожного варіанту vi/, vimякий ви коли-небудь використовуєте; наприклад,

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

Зовнішній набір дужок створює додаткову оболонку, тому ручна зміна значення SHLVL не забруднює поточне середовище (батьківське) оболонки. Звичайно, commandключове слово є для запобігання виклику функцій (що призведе до нескінченного циклу рекурсії). І звичайно, ви повинні помістити ці визначення у свій .bashrcчи інший файл ініціалізації оболонки.


Тут є невелика неефективність. У деяких снарядах, якщо ви скажете

( cmd 1 ;  cmd 2 ;;  cmd n )

де є зовнішня, виконувана програма (тобто не вбудована команда), оболонка зберігає додатковий процес, що лежить навколо, лише для того, щоб дочекатися його завершення. Це (певно) не обов’язково; переваги та недоліки дискусійні. Якщо ви не проти зав'язати трохи пам’яті та слот для процесу (і побачити ще один процес оболонки, ніж вам потрібно, коли ви робите ), виконайте вищесказане та перейдіть до наступного розділу. Дітто, якщо ви використовуєте оболонку, яка не підтримує зайвий процес навколо. Але, якщо ви хочете уникнути зайвого процесу, спершу потрібно спробуватиcmdncmdnps

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

execКоманда там , щоб запобігти додатковий процес оболонки від затяжною.

Але, є єва. Обробка оболонки SHLVLдещо інтуїтивно зрозуміла: коли оболонка запускається, вона перевіряє, чи SHLVLвстановлено. Якщо його не встановлено (або встановлено щось інше, ніж число), оболонка встановлює його на 1. Якщо він встановлений (до числа), оболонка додає до нього 1.

Але, за цією логікою, якщо ви скажете exec sh, ви SHLVLповинні піти вгору. Але це небажано, оскільки ваш реальний рівень оболонки не збільшився. Оболонка обробляє це шляхом вирахування одного з SHLVL коли ви робите 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

Тому

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

є миття; він збільшується SHLVLлише для того, щоб знову його зменшити. Ви можете просто сказати vim, без користі функції.

Примітка:
За словами Стефана Шазеласа (який знає все) , деякі снаряди досить розумні, щоб цього не робити, якщо він execзнаходиться в нижній частині корпусу .

Щоб виправити це, ви зробили б

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

Потім я побачив, що ви хочете рахувати vimрівні незалежно від рівнів оболонки. Ну, точно такий же трюк працює (ну, з незначною модифікацією):

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

(І так далі для vi, viewі т.д.), exportнеобхідно тому , що VILVLне визначений в якості змінної середовища за замовчуванням. Але це не повинно бути частиною функції; ви можете просто сказати export VILVLяк окрему команду (у своєму .bashrc). І, як було сказано вище, якщо процес додаткової оболонки не є проблемою для вас, ви можете зробити це command vimзамість цього exec vimі залишити в SHLVLспокої:

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

Особисті переваги:
Ви можете перейменувати VILVLщось на зразок VIM_LEVEL. Коли я дивлюсь на " VILVL", мені болять очі; вони не можуть сказати, чи це неправильне написання "вінілу" чи неправильна римська цифра.


Якщо ви використовуєте оболонку, яка не підтримує SHLVL(наприклад, тире), ви можете реалізувати її самостійно до тих пір, поки оболонка реалізує файл запуску. Просто роби щось подібне

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

у вашому .profileабо застосовному файлі. (Напевно, ви не повинні використовувати ім'я SHLVL, оскільки це спричинить хаос, якщо ви коли-небудь почнете використовувати оболонку, яка підтримує SHLVL.)


Інші відповіді стосувалися питання вбудовування значень змінних середовищ у ваш запит на оболонку, тому я не повторюю це, особливо ви кажете, що вже знаєте, як це зробити.


1
Я дещо спантеличений тим, що так багато відповідей пропонують виконати зовнішню виконувану програму, наприклад, psабо pstree, коли ви можете зробити це за допомогою вбудованих оболонок.
Скотт

Ця відповідь ідеальна. Я позначив це як рішення (на жаль, ще не так багато голосів).
Пранай

Ваш підхід є дивовижним, і ви використовуєте лише примітиви, а це означає, що включення цього до мого .profile / .shellrc нічого не порушить. Я витягую їх на будь-якій машині, над якою працюю.
Праней

1
Зауважимо, що dashмає арифметичне розширення. SHELL_LEVEL=$((SHELL_LEVEL + 1))повинно вистачити, навіть якщо $ SHELL_LEVEL раніше не було встановлено або порожньо. Це тільки якщо ви повинні були бути портативними до оболонки Bourne , що ви повинні вдатися до expr, але тоді вам також необхідно замінити $(...)з `..`. SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Стефан Шазелас

2
@Pranay, це навряд чи буде проблемою. Якщо зловмисник може ввести будь-яку довільну env var, то такі речі, як PATH / LD_PRELOAD, є більш очевидним вибором, але якщо непроблемні змінні проходять, як, наприклад, sudo, налаштований без reset_env (і можна змусити bashсценарій читати ~ / .bashrc by наприклад, зробити stdin сокетом), то це може стати проблемою. Це багато "якщо", але щось слід тримати в глузді розуму (несаніфіковані дані в арифметичному контексті небезпечно)
Stéphane Chazelas

37

Ви можете порахувати стільки часу, скільки потрібно, щоб підняти дерево процесу, поки не знайдете лідера сесії. Як і zshв 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
}

Або POSIXly (але менш ефективно):

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

Це дасть 0 для оболонки, яку запустив ваш емулятор терміналу або getty, і ще один для кожного нащадка.

Це потрібно зробити лише один раз при запуску. Наприклад, з:

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

у вашому ~/.zshrcабо еквівалентному, щоб мати його у своєму запиті.

tcshі кілька інших оболонок ( zsh, ksh93, fishі , по bashкрайней мере) зберегти $SHLVLзмінну, вони прирощення при запуску (і декремента перед запуском наступної команди з exec(якщо це execне в субоболочке , якщо вони не глючить (але багато з них))). Це відстежує лише кількість вкладених оболонок, але не обробляє гніздування. Також рівень 0 не гарантується як лідер сесії.


Так ... це чи подібне. Я не хотів писати це самостійно, і не було наміру когось писати це для мене. :(. Я сподівався на якусь функцію vim або shell або якийсь плагін, який регулярно підтримується. Я шукав, але щось не знайшов.
Pranay

31

Використовуйте echo $SHLVL. Використовуйте принцип KISS . Залежно від складності вашої програми, цього може бути достатньо.


2
Працює для bash, але не для dash.
agc

SHLVL мені не допомагає. Я знав про це, і він також з’явився в пошуку, коли я шукав. :) Більше деталей у питанні.
Праней

@Pranay Ви впевнені, що сам vim не надає цю інформацію?
user2497

@ user2497, я дещо. Це передумова питання. Я шукав всюди, я знав і про SHLVL. Я хотів -> а) бути певним, що такого немає. б) робіть це з найменшою кількістю залежностей / обслуговування.
Праней

16

Одним з потенційних рішень є пошук результатів pstree. Пробігаючи всередині оболонки, яка була породжена зсередини vi, частина дерева дерева, що перераховується, pstreeповинна показувати, наскільки ви глибокі. Наприклад:

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

Так, це я запропонував як своє рішення (у питанні). Я не хочу розбирати pstree :(. Це добре для ручного читання, я думав написати програму, щоб зробити це для мене, і дайте мені знати. Я не дуже схильний написати парсер, якщо плагін / інструмент це вже робить :).
Праней

11

Перший варіант - лише глибина оболонки.

Просте рішення для bash: додайте до .bashrcнаступних двох рядків (або змініть поточне PS1значення):

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

Результат:

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

Число на початку рядка підказок буде позначати рівень оболонки.

Другий варіант, з вкладеними рівнями vim та оболонки.

додати ці рядки до .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

Результат:

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 - рівень глибини vim
s: 3 - рівень глибини оболонки


це дасть мені гніздування баш. Це не дасть мені гнізд. :)
Праней

@Pranay Перевірте нове рішення. Це робить те, що ти хочеш.
MiniMax

Так, це хороше рішення. Я можу додати ще оболонок, і це спрацювало б :).
Праней

8

У запитанні, про яке ви згадали розбір pstree. Ось порівняно простий спосіб:

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

В pstreeопції:

  • -A- ASCII вихід для легшої фільтрації (у нашому випадку перед кожною командою передує `-)
  • -a - показуйте також аргументи команд, оскільки побічний ефект кожна команда відображається в окремому рядку, і ми можемо легко фільтрувати вихід, використовуючи grep
  • -l - не обрізайте довгих рядків
  • -s- показати батькам вибраного процесу
    (на жаль, не підтримується у старих версіях pstree)
  • $$ - обраний процес - PID поточної оболонки

Так, я робив це дуже багато. У мене також було що порахувати "bash" і "vim" і т. Д. Я просто не хотів його підтримувати. Також не представляється можливим мати багато спеціальних функціональних можливостей, коли вам доводиться перемикати багато віртуальних машин та інколи розвиватися на них.
Праней

3

Це не відповідає чітко на це питання, але в багатьох випадках це може зробити непотрібним:

Коли ви вперше запустили свою оболонку, запустіть set -o ignoreeof. Не вкладайте його у своє ~/.bashrc.

Візьміть за звичку вводити Ctrl-D, коли ви думаєте, що перебуваєте в оболонці верхнього рівня і хочете бути впевненими.

Якщо ви не перебуваєте в оболонці верхнього рівня, Ctrl-D подасть сигнал "кінець введення" до поточної оболонки, і ви повернетеся на один рівень назад.

Якщо ви знаходитесь в оболонці верхнього рівня, ви отримаєте повідомлення:

Use "logout" to leave the shell.

Я використовую це весь час для прикутих сеансів SSH, щоб полегшити перехід до певного рівня ланцюга SSH. Він також працює для вкладених оболонок.


1
Це безумовно допомагає і так, це зніме масу ускладнень :). Я можу просто поєднати це з прийнятою відповіддю :)). Умовно встановлений, тому мені, можливо, не доведеться весь час дивитися на своє підказки.
Праней
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.