我有多少贝壳?

Pra*_*nay 76 shell bash vim zsh shell-script

问题:找出我有多少贝壳深。

详细信息:我经常从 vim 中打开外壳。构建并运行并退出。有时我忘记在里面打开另一个vim,然后打开另一个shell。:(

我想知道我有多少贝壳,甚至可能一直在我的贝壳屏幕上显示它。(我可以管理那部分)。

我的解决方案:解析进程树并查找 vim 和 bash/zsh 并找出当前进程在其中的深度。

这样的东西已经存在了吗?我找不到任何东西。

Sco*_*ott 47

当我读到你的问题时,我的第一个想法是$SHLVL。然后,我看到你想算vim的水平 ,除了外壳的水平。一个简单的方法是定义一个shell函数:

vim()  { ( ((SHLVL++)); command vim  "$@");}
Run Code Online (Sandbox Code Playgroud)

SHLVL 每次键入vim命令时,这都会自动且无提示地递增。您将需要为您曾经使用过的vi/ 的每个变体执行此操作vim;例如,

vi()   { ( ((SHLVL++)); command vi   "$@");}
view() { ( ((SHLVL++)); command view "$@");}
Run Code Online (Sandbox Code Playgroud)

外面的一组括号创建了一个子shell,因此手动更改 的值SHLVL 不会污染当前(父)shell 环境。当然,command关键字是为了防止函数调用自己(这会导致无限递归循环)。当然,您应该将这些定义放入您的.bashrc或其他 shell 初始化文件中。


上面有一点效率低下。在某些 shell 中(bash 是其中之一),如果你说

( cmd 1 ;  cmd 2 ; ... ;  cmd n )

如果是外部的可执行程序(即不是内置命令),shell 会保留一个额外的进程,只是为了等待终止。这(可以说)没有必要;优点和缺点值得商榷。如果您不介意占用一点内存和一个进程槽(并且在执行 a 时看到比您需要的更多的 shell 进程),那么请执行上述操作并跳到下一部分。如果您使用的外壳不会保留额外的进程,则同上。但是,如果你想避免额外的过程,首先要尝试的是cmdncmdnps

vim()  { ( ((SHLVL++)); exec vim  "$@");}
Run Code Online (Sandbox Code Playgroud)

exec命令是为了防止额外的 shell 进程逗留。

但是,有一个问题。shell 对 的处理SHLVL有点直观:当 shell 启动时,它会检查是否SHLVL已设置。如果未设置(或设置为数字以外的其他值),shell 会将其设置为 1。如果已设置(设置为数字),shell 会将其加 1。

但是,按照这个逻辑,如果你说exec sh,你SHLVL应该上升。但这是不可取的,因为您的真实外壳级别并没有增加。外壳通过从执行以下操作减去一个 处理此问题:SHLVLexec

$ 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
Run Code Online (Sandbox Code Playgroud)

所以

vim()  { ( ((SHLVL++)); exec vim  "$@");}
Run Code Online (Sandbox Code Playgroud)

是洗;它增加SHLVL只会再次减少它。您不妨直接说vim,而没有功能的好处。

注意:
根据 Stéphane Chazelas(他什么都知道)的说法,如果位于子shell 中,一些shell 足够聪明,会这样做exec

要解决这个问题,你会这样做

vim()  { ( ((SHLVL+=2)); exec vim  "$@");}
Run Code Online (Sandbox Code Playgroud)

然后我看到,你想算vim水平 独立的外壳水平。好吧,完全相同的技巧有效(好吧,稍作修改):

vim() { ( ((SHLVL++, VILVL++)); export VILVL; exec vim "$@");}
Run Code Online (Sandbox Code Playgroud)

(对于viview等,依此类推)export是必需的,因为VILVL默认情况下未将其定义为环境变量。但它不需要成为函数的一部分;你可以只说export?VILVL一个单独的命令(在你的.bashrc)。并且,如上所述,如果额外的 shell 进程对您来说不是问题,您可以使用command?vim代替exec?vim,并且不用管SHLVL

vim() { ( ((VILVL++)); command vim "$@");}
Run Code Online (Sandbox Code Playgroud)

个人偏好:
您可能希望重命名VILVLVIM_LEVEL. 看着“ VILVL”,我的眼睛很痛;他们无法分辨这是“vinyl”的拼写错误还是格式错误的罗马数字。


如果您使用的外壳不支持SHLVL(例如,破折号),只要外壳实现启动文件,您就可以自己实现它。只是做类似的事情

if [ "$SHELL_LEVEL" = "" ]
then
    SHELL_LEVEL=1
else
    SHELL_LEVEL=$(expr "$SHELL_LEVEL" + 1)
fi
export SHELL_LEVEL
Run Code Online (Sandbox Code Playgroud)

在您的.profile或适用的文件中。(您可能不应该使用 name SHLVL,因为如果您开始使用支持 的 shell,这会导致混乱SHLVL。)


其他答案已经解决了将环境变量值嵌入到 shell 提示中的问题,所以我不会重复,尤其是你说你已经知道怎么做。

  • @Pranay,这不太可能成为问题。如果攻击者可以注入 _any_ 任意 env var,那么诸如 PATH/LD_PRELOAD 之类的东西是更明显的选择,但是如果没有问题的变量通过,例如 sudo 配置而没有 reset_env(并且可以强制 `bash` 脚本读取 ~/ .bashrc 通过使 stdin 成为一个套接字为例),那么这可能会成为一个问题。这是很多“如果”,但有些东西要牢记在心(算术上下文中未经消毒的数据是危险的) (2认同)

Sté*_*las 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
}
Run Code Online (Sandbox Code Playgroud)

或 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"
)
Run Code Online (Sandbox Code Playgroud)

这将为您的终端模拟器或 getty 启动的 shell 提供 0,并为每个后代提供一个。

您只需要在启动时执行一次。例如:

PS1="[$(lvl)]$PS1"
Run Code Online (Sandbox Code Playgroud)

在您的~/.zshrc或等效的提示中包含它。

tcsh和其他几发炮弹(zshksh93fishbash至少)保持$SHLVL他们在启动时增加变量(运行另一个命令之前和递减exec(除非exec是在子shell,如果他们不是越野车(但很多)))。这只跟踪外壳嵌套的数量,而不是进程嵌套。同样,级别 0 也不能保证是会话领导者。


use*_*497 32

使用echo $SHLVL. 使用KISS 原则。根据您的程序的复杂性,这可能就足够了。

  • 适用于`bash`,但不适用于`dash`。 (2认同)

Joh*_*ohn 16

一种可能的解决方案是查看 的输出pstree。当在从 inside 产生的 shell 中运行时vi,列出的树树部分pstree应该告诉你你有多深。例如:

$ pstree <my-user-ID>
...
       ??gnome-terminal-???bash???vi???sh???vi???sh???pstree
...
Run Code Online (Sandbox Code Playgroud)


Min*_*Max 11

第一个变体 - 仅壳深度。

简单的解决方案bash:添加到.bashrc接下来的两行(或更改您的当前PS1值):

PS1="${SHLVL} \w\$ "
export PS1
Run Code Online (Sandbox Code Playgroud)

结果:

1 ~$ bash
2 ~$ bash
3 ~$ exit
exit
2 ~$ exit
exit
1 ~$
Run Code Online (Sandbox Code Playgroud)

提示字符串开头的数字表示shell级别。

第二个变体,具有嵌套的 vim 和 shell 级别。

将此行添加到 .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
Run Code Online (Sandbox Code Playgroud)

结果:

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$
Run Code Online (Sandbox Code Playgroud)

v:1 - vim 深度级别
s:3 - shell 深度级别


pab*_*ouk 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
Run Code Online (Sandbox Code Playgroud)

pstree选项:

  • -A- ASCII 输出以便于过滤(在我们​​的例子中,每个命令都以 开头`-
  • -a - 还显示命令参数,作为副作用,每个命令都显示在单独的行中,我们可以使用以下命令轻松过滤输出 grep
  • -l - 不要截断长行
  • -s- 显示所选进程的父进程
    (遗憾的是旧版本不支持pstree
  • $$ - 选择的进程 - 当前 shell 的 PID