Quantas conchas de profundidade eu sou?

73

Problema : descubra quantas conchas eu tenho.

Detalhes : Eu abro muito o shell do vim. Construa, execute e saia. Às vezes eu esqueço e abro outro vim dentro e depois outro shell. :(

Quero saber quantas conchas de profundidade tenho, talvez até tê-la na tela do meu shell o tempo todo. (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.

Algo assim já existe? Não consegui encontrar nada.

Pranay
fonte
27
A $SHLVLvariável (mantida por várias conchas) é o que você está procurando?
Stéphane Chazelas
11
Para esclarecer, você não está realmente interessado em quantas conchas (aninhadas diretamente), indicadas pelo SHLVL, mas se o seu shell atual é um descendente do vim?
Jeff Schaller
14
Isso parece ser um problema XY - meu fluxo de trabalho é ^ Z para escapar da instância do vim para o shell pai e fgvoltar, o que não tem esse problema.
MaçanetaJan
2
@ Doorknob, eu também faço isso. mas eu prefiro este, porque então eu teria que continuar verificando "trabalhos". e Pode haver muitas vezes funcionando na minha máquina. agora, adicione o TMUX com a equação. Torna-se complexo e transbordante. Se eu gerar casca dentro do vim, seria menos disperso. (No entanto, acabo fazendo a bagunça e, portanto, a pergunta).
Pranay
3
@ Doorknob: Com todo o respeito, isso parece responder à pergunta: “Como dirijo do ponto A ao ponto B?” Com a sugestão: “Não dirija; basta usar um Uber. ”Se o usuário tiver um fluxo de trabalho que envolva a edição de vários arquivos simultaneamente, ter vários vimtrabalhos paralelos interrompidos pode ser mais confuso do que ter uma pilha de processos aninhados. A propósito, prefiro ter várias janelas , para poder ir e voltar rapidamente com facilidade, mas não chamaria isso de problema XY apenas porque prefiro um fluxo de trabalho diferente.
Scott

Respostas:

45

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

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

Isso aumentará automaticamente e silenciosamente SHLVL toda vez que você digitar um vimcomando. Você precisará fazer isso para cada variante de vi/ vimque você usar; por exemplo,

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

O conjunto externo de parênteses cria um subshell, portanto, a alteração manual no valor de SHLVL não contamina o ambiente atual do shell (pai). Obviamente, a commandpalavra-chave existe para impedir que as funções se chamem (o que resultaria em um loop infinito de recursão). E é claro que você deve colocar essas definições no seu .bashrcou em outro arquivo de inicialização do shell.


Há uma ligeira ineficiência no exposto acima. Em algumas conchas (bash sendo uma), se você disser

( cmd 1 ;  cmd 2 ;;  cmd n )

Onde é um programa executável externo (ou seja, não um comando interno), o shell mantém um processo extra por aí, apenas para aguardar o término. Isso é (sem dúvida) desnecessário; as vantagens e desvantagens são discutíveis. Se você não se importa em amarrar um pouco de memória e um slot de processo (e ver mais um processo de shell do que o necessário quando faz um ), faça o acima e pule para a próxima seção. O mesmo se você estiver usando um shell que não mantém o processo extra por aí. Mas, se você quiser evitar o processo extra, a primeira coisa a tentar écmdncmdnps

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

O execcomando existe para impedir que o processo de shell extra permaneça.

Mas, há uma pegadinha. O manuseio do shell SHLVLé um tanto intuitivo: quando o shell inicia, ele verifica se SHLVLestá definido. Se não estiver definido (ou definido como algo diferente de um número), o shell o definirá como 1. Se estiver definido (como um número), o shell adicionará 1 a ele.

Mas, por essa lógica, se você diz exec sh, você SHLVLdeve subir. Mas isso é indesejável, porque o 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

assim

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

é uma lavagem; ele é incrementado SHLVLapenas para diminuí-lo novamente. Você pode apenas dizer vim, sem o benefício de uma função.

Nota:
De acordo com Stéphane Chazelas (quem sabe tudo) , algumas conchas são inteligentes o suficiente para não fazer isso se execestiverem em um subshell.

Para consertar isso, você faria

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

Então vi que você queria contar vimníveis independentemente dos níveis do shell. Bem, exatamente 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.) A exporté necessária porque VILVLnã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 VILVLcomo um comando separado (no seu .bashrc). E, como discutido acima, se o processo extra de shell não for um problema para você, você pode fazer em command vimvez de exec vime deixar em SHLVLpaz:

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

Preferência pessoal:
convém renomear VILVLpara algo assim VIM_LEVEL. Quando olho para " VILVL", meus olhos doem; eles não sabem dizer se é um erro de ortografia de "vinil" ou um número romano malformado.


Se você estiver usando um shell que não suporta SHLVL(por exemplo, traço), poderá implementá-lo você mesmo, desde que o shell implemente um arquivo de inicialização. Basta fazer algo como

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

no seu .profilearquivo ou aplicável. (Você provavelmente não deve usar o nome SHLVL, pois isso causará caos se você começar a usar um shell compatível SHLVL.)


Outras respostas abordaram a questão de incorporar valores da variável de ambiente no prompt do shell, então não vou repetir isso, especialmente você diz que já sabe como fazê-lo.

Scott
fonte
11
Estou um pouco confuso que tantas respostas sugerem a execução de um programa executável externo, como psou pstree, quando você pode fazer isso com os shell builtins.
Scott
Essa resposta é perfeita. Marquei isso como a solução (infelizmente ainda não há muitos votos).
Pranay
Sua abordagem é incrível e você está usando apenas as primitivas, o que significa que incluir isso no meu .profile / .shellrc não quebraria nada. Eu puxo aqueles em qualquer máquina em que trabalho.
Pranay
11
Observe que dashpossui expansão aritmética. SHELL_LEVEL=$((SHELL_LEVEL + 1))deve ser suficiente, mesmo que $ SHELL_LEVEL estivesse anteriormente desmarcado ou vazio. É somente se você tivesse que ser portátil para o shell Bourne que você precisa de recorrer a expr, mas então você também precisaria para substituir $(...)com `..`. SHELL_LEVEL=`expr "${SHELL_LEVEL:-0}" + 1`
Stéphane Chazelas
2
@ Pray, é improvável que seja um problema. Se um invasor pode injetar qualquer env var arbitrário, coisas como PATH / LD_PRELOAD são escolhas mais óbvias, mas se variáveis ​​não-problemáticas passarem, como o sudo configurado sem reset_env (e você pode forçar um bashscript a ler ~ / .bashrc por tornando stdin um soquete, por exemplo), isso pode se tornar um problema. Isso é um monte de "se" s, mas algo para se manter na parte de trás da mente (dados unsanitized em contexto aritmética é perigoso)
Stéphane Chazelas
37

Você pode contar quantas vezes precisar para subir na árvore de processos até encontrar um líder de sessão. Como zshno 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 iniciado pelo emulador de terminal ou 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 ~/.zshrcou equivalente para tê-lo em seu prompt.

tcshe vários outros conchas ( zsh, ksh93, fishe bashpelo menos) manter uma $SHLVLvariável que incrementar na inicialização (e decréscimo antes de executar outro comando com exec(a menos que execseja em um subshell se eles não estão de buggy (mas muitos são))). Isso apenas rastreia a quantidade de aninhamento de shell , não o processo de aninhamento. Também não é garantido que o nível 0 seja o líder da sessão.

Stéphane Chazelas
fonte
Sim .. isso ou similar. Eu não queria escrever isso sozinho, e não era minha intenção que alguém escrevesse isso para mim. . :( Eu estava esperando por algum recurso no vim ou shell ou algum plugin que é mantido regularmente I procurou, mas não encontrou alguma coisa..
Pranay
31

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

user2497
fonte
2
Funciona para bash, mas não para dash.
agc 27/06
SHLVL não me ajuda. Eu sabia disso e também apareceu na pesquisa quando procurei. :) Há mais detalhes na pergunta.
Pranay
@Pranay Você tem certeza de que o próprio vim não fornece essas informações?
user2497
@ user2497, estou um pouco. Essa é a premissa da pergunta. Eu procurei em todos os lugares, eu também sabia do SHLVL. Eu queria -> a) ter certeza de que não existe. b) faça-o com menor quantidade de dependências / manutenção.
Pranay
16

Uma solução potencial é olhar para a saída de pstree. Quando executado dentro de uma concha gerada por dentro vi, a parte da árvore listada pstreedeve mostrar a sua profundidade. Por exemplo:

$ pstree <my-user-ID>
...
       ├─gnome-terminal-─┬─bash───vi───sh───vi───sh───pstree
...
John
fonte
Sim, foi o que sugeri como minha solução (na pergunta). Porém, não desejo analisar o pstree :(. Isso é bom para lê-lo manualmente, eu estava pensando em escrever um programa para fazer isso por mim e me informar. Não estou muito inclinado a escrever um analisador se um plugin / ferramenta já faz isso :).
Pranay
11

Primeira variante - apenas profundidade da casca.

Solução simples para bash: adicionar às .bashrcpróximas duas linhas (ou alterar seu PS1valor atual ):

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 será denotado no 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 do vim
s: 3 - nível de profundidade do shell

MiniMax
fonte
isso me dará o aninhamento de festança. Não vai me dar os ninhos de vim. :)
Pranay
@Pranay Verifique a nova solução. Está fazendo o que você quer.
MiniMax 27/06
Sim, esta é uma boa solução. Eu posso adicionar mais conchas e funcionaria :).
Pranay
8

Na pergunta que 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 pstreeopções:

  • -A- Saída ASCII para filtragem mais fácil (no nosso caso, todos os comandos são precedidos por `-)
  • -a - show também argumentos de comando, como efeito colateral, todos os comandos são mostrados em uma linha separada e podemos filtrar facilmente a saída usando grep
  • -l - não trunque linhas longas
  • -s- mostra os pais do processo selecionado
    (infelizmente não é suportado nas versões antigas do pstree)
  • $$ - o processo selecionado - o PID do shell atual
pabouk
fonte
Sim, eu estava fazendo isso praticamente. Eu também tinha algo para contar "bash" e "vim" etc. Apenas não desejava mantê-lo. Também não é viável ter muitas funcionalidades personalizadas quando você precisa alternar várias VMs e desenvolvê-las algumas vezes.
Pranay
3

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

Quando você iniciar seu shell, execute set -o ignoreeof. Não coloque no seu ~/.bashrc.

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

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

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

Use "logout" to leave the shell.

Eu uso isso o tempo todo em sessões SSH encadeadas, para facilitar o retorno a um nível específico da cadeia SSH. Também funciona para conchas aninhadas.

Curinga
fonte
11
Definitivamente, isso ajuda e, sim, ele remove muitas complicações :). Eu posso apenas combinar isso com a resposta aceita :)). Definido condicionalmente, portanto, talvez não seja necessário olhar o meu prompt o tempo todo.
Pranay