Quantas conchas eu sou profundo?

72

Problema : Descobre quantas conchas eu sou.

Detalhes : Eu abro muito o shell do vim. Construa e corra e saia. Às vezes eu esqueço e abro outro vim dentro e depois outro invólucro. : (

Eu quero saber quantas conchas eu sou profunda, talvez até mesmo ter na minha tela shell em todos os momentos. (Eu posso gerenciar essa parte).

Minha solução : Analise a árvore de processos e procure por vim e bash / zsh e descubra a profundidade do processo atual dentro dela.

Será que algo assim já existe? Eu não consegui encontrar nada.

    
por Pranay 27.06.2017 / 18:27

7 respostas

44

Quando leio sua pergunta, meu primeiro pensamento foi $SHLVL . Então vi que você queria contar vim levels além dos níveis de shell. Uma maneira simples de fazer isso é definir uma função de shell:

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

Isso incrementará automática e silenciosamente SHLVL cada vez que você digita um comando vim . Você precisará fazer isso para cada variante de vi / vim que você já usou; por exemplo,

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

O conjunto externo de parênteses cria um subshell, então a mudança manual no valor de SHLVL não contamina o ambiente de shell atual (pai). É claro que a palavra-chave command está lá para evitar as funções de se chamarem (o que resultaria em um loop de recursão infinito). E é claro que você deve colocar essas definições no seu .bashrc ou outro arquivo de inicialização.

Existe uma ligeira ineficiência no que precede. Em algumas conchas (bash sendo um), se você disser

(cmd1; cmd2;; cmdn)

onde cmdn é um programa externo e executável (ou seja, não é um comando interno), o shell mantém um processo extra por aí, apenas para esperar que cmdn termine. Isso é (sem dúvida) desnecessário; as vantagens e desvantagens são discutíveis. Se você não se importar de ocupar um pouco de memória e um espaço de processo (e para ver mais um processo de shell do que você precisa quando faz um ps ), então faça o acima e pule para a próxima seção. Ditto se você estiver usando uma casca que não mantém o processo extra por aí. Mas, se você quiser evitar o processo extra, uma primeira coisa a tentar é

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

O comando exec está lá para evitar que o processo de shell extra permaneça.

Mas há uma pegadinha. A manipulação do shell de SHLVL é um pouco intuitiva: Quando o shell é iniciado, ele verifica se SHLVL está definido. Se não estiver definido (ou definido para algo que não seja um número), o shell define para 1. Se estiver definido (para um número), o shell adiciona 1 a ele.

Mas, com essa lógica, se você disser exec sh , seu SHLVL deve aumentar. Mas isso é indesejável, porque seu nível real de shell não aumentou. O shell lida com isso subtraindo um de SHLVL quando você faz um 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

Então

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

é uma lavagem; Ele incrementa SHLVL apenas para decrementar novamente. Você pode muito bem dizer apenas vim , sem o benefício de uma função.

Note:
According to Stéphane Chazelas (who knows everything), some shells are smart enough not to do this if the exec is in a subshell.

Para corrigir isso, você faria

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

Então vi que você queria contar vim levels independentemente dos níveis de shell. Bem, o mesmo truque funciona (bem, com uma pequena modificação):

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

(e assim por diante para vi , view , etc.) O export é necessário porque VILVL não é definido como uma variável de ambiente por padrão. Mas não precisa fazer parte da função; você pode apenas dizer export VILVL como um comando separado (no seu .bashrc ). E, como discutido acima, se o processo de shell extra não for um problema para você, você pode fazer command vim em vez de exec vim e deixar SHLVL sozinho:

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

Personal Preference:
You may want to rename VILVL to something like VIM_LEVEL.  When I look at “VILVL”, my eyes hurt; they can’t tell whether it’s a misspelling of “vinyl” or a malformed Roman numeral.

Se você estiver usando um shell que não suporta SHLVL (por exemplo, traço), você pode implementá-lo sozinho, desde que o shell implemente um arquivo de inicialização. Apenas faça algo como

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

no seu .profile ou arquivo aplicável. (Você provavelmente não deve usar o nome SHLVL , pois isso causará o caos se você já começou a usar um shell que suporta SHLVL .)

Outras respostas abordaram o problema de incorporar valores de variáveis de ambiente em seu prompt de shell, então eu não vou repetir isso, especialmente você diz que já sabe como fazer isso.

    
por 29.06.2017 / 00:15
37

Você pode contar quantas vezes precisar subir na árvore de processos até encontrar um líder de sessão. Como com zsh no 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
}

Ou POSIXly (mas menos eficiente):

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

Isso daria 0 para o shell que foi iniciado pelo emulador de terminal ou pelo getty e mais um para cada descendente.

Você só precisa fazer isso uma vez na inicialização. Por exemplo com:

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

no seu ~/.zshrc ou equivalente para tê-lo em seu prompt.

tcsh e vários outros shells ( zsh , ksh93 , fish e bash pelo menos) mantêm uma variável $SHLVL que eles incrementam na inicialização (e decrementam antes de executar outro comando com exec (a menos que exec esteja em uma subshell se eles não tiverem bugs (mas muitos são))). Isso só rastreia a quantidade de aninhamento de shell , mas não aninhamento de processo. Também não é garantido que o nível 0 seja o líder da sessão.

    
por 27.06.2017 / 18:47
31

Use echo $SHLVL . Use o princípio KISS . Dependendo da complexidade do seu programa, isso pode ser suficiente.

    
por 27.06.2017 / 18:55
16

Uma solução potencial é examinar a saída de pstree . Quando executado dentro de um shell que foi gerado a partir de vi , a parte da árvore de árvores que lista pstree deve mostrar o quão profundo você está. Por exemplo:

$ pstree <my-user-ID>
...
       ├─gnome-terminal-─┬─bash───vi───sh───vi───sh───pstree
...
    
por 27.06.2017 / 18:35
11

Primeira variante - somente profundidade da casca.

Solução simples para bash : adicione às .bashrc próximas duas linhas (ou altere seu valor atual de PS1 ):

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

Resultado:

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

O número no início da string de prompt indica o nível do shell.

Segunda variante, com níveis aninhados de vim e shell.

adicione estas linhas ao .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

Resultado:

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 - nível de profundidade vim
s: 3 - nível de profundidade da concha

    
por 27.06.2017 / 19:40
8

Na pergunta você mencionou a análise de pstree . Aqui está uma maneira relativamente simples:

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

As opções pstree :

  • -A - saída ASCII para filtragem mais fácil (no nosso caso, cada comando é precedido por '- )
  • -a - mostra também argumentos de comando, como um efeito colateral, cada comando é mostrado em uma linha separada e podemos facilmente filtrar a saída usando grep
  • -l - não trunca linhas longas
  • -s - mostrar aos pais o processo selecionado (infelizmente não suportado em versões antigas de pstree )
  • $$ - o processo selecionado - o PID do shell atual
por 28.06.2017 / 13:59
3

Isso não estritamente responde à pergunta, mas em muitos casos pode ser desnecessário:

Quando você iniciar seu shell pela primeira vez, execute set -o ignoreeof . Não coloque em seu ~/.bashrc .

Crie o hábito de digitar Ctrl-D quando achar que está no nível superior e quiser ter certeza.

Se você não estiver no shell de nível superior, o Ctrl-D sinalizará "fim de entrada" para o shell atual e você voltará um nível.

Se você estiver no shell de nível superior, você receberá uma mensagem:

Use "logout" to leave the shell.

Eu uso isso o tempo todo para sessões SSH encadeadas, para facilitar o retorno a um nível específico da cadeia SSH. Ele funciona para shells aninhados também.

    
por 29.06.2017 / 22:01