Pergunta

Eu tenho um script de shell que executa o mesmo comando em vários diretórios (fgit).Para cada diretório, eu gostaria de mostrar a atual linha de comandos + o comando que será executado lá.Como faço para obter a seqüência de caracteres que corresponde ao decodificado (expandido)PS1?Por exemplo, meu padrão PS1 é

${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$

e eu gostaria de eco resultante de comandos username@hostname:/path$, de preferência (mas não necessariamente) com as cores agradáveis.Uma olhada superficial no Bash manual não revelar qualquer resposta definitiva, e echo -e $PS1 só avalia as cores.

Foi útil?

Solução

Uma grande vantagem do software de fonte aberta é que a origem é, assim, abrir :-)

Bash si só, não fornece essa funcionalidade, mas existem vários truques que você pode usar para fornecer um subconjunto (como a substituição de \u com $USER e assim por diante).No entanto, isto requer um monte de duplicação de funcionalidade e assegurar que o código é mantido em sincronia com o que bash não no futuro.

Se você deseja obter todos o poder do prompt de variáveis (e você não se importa de sujar as mãos com um pouco de codificação (e, se você não mente, por que você está aqui?)), é fácil o suficiente para adicionar ao próprio shell.

Se você baixar o código para bash (Eu estou olhando para a versão 4.2), há uma y.tab.c arquivo que contém a decode_prompt_string() função:

char *decode_prompt_string (string) char *string; { ... }

Esta é a função que avalia a PSx variáveis para avisar.Para permitir essa funcionalidade para ser fornecido aos usuários de shell em si (ao invés de incluir somente usado por a shell), você pode seguir estas etapas para adicionar um comando interno evalps1.

Primeiro, altere support/mkversion.sh para que você não confundi-lo com um "real" bash, e , para que a FSF pode negar todo o conhecimento para fins de garantia :-) Basta alterar uma linha (eu adicionei o -pax bits):

echo "#define DISTVERSION \"${float_dist}-pax\""

Segundo, a mudança builtins/Makefile.in para adicionar um novo arquivo de origem.Isso implica uma série de passos.

(a) Adicionar $(srcdir)/evalps1.def para o fim de DEFSRC.

(b) Adicionar evalps1.o para o fim de OFILES.

(c) Adicionar as dependências necessárias:

evalps1.o: evalps1.def $(topdir)/bashtypes.h $(topdir)/config.h \
           $(topdir)/bashintl.h $(topdir)/shell.h common.h

Terceiro, adicione o builtins/evalps1.def arquivo em si, este é o código que é executado quando você executar o evalps1 comando:

This file is evalps1.def, from which is created evalps1.c.
It implements the builtin "evalps1" in Bash.

Copyright (C) 1987-2009 Free Software Foundation, Inc.

This file is part of GNU Bash, the Bourne Again SHell.

Bash is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Bash is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Bash.  If not, see <http://www.gnu.org/licenses/>.

$PRODUCES evalps1.c

$BUILTIN evalps1
$FUNCTION evalps1_builtin
$SHORT_DOC evalps1
Outputs the fully interpreted PS1 prompt.

Outputs the PS1 prompt, fully evaluated, for whatever nefarious purposes
you require.
$END

#include <config.h>
#include "../bashtypes.h"
#include <stdio.h>
#include "../bashintl.h"
#include "../shell.h"
#include "common.h"

int
evalps1_builtin (list)
     WORD_LIST *list;
{
  char *ps1 = get_string_value ("PS1");
  if (ps1 != 0)
  {
    ps1 = decode_prompt_string (ps1);
    if (ps1 != 0)
    {
      printf ("%s", ps1);
    }
  }
  return 0;
}

A massa de que é a licença GPL (desde que eu modificado a partir exit.def), com uma função simples no final para obter e interpretar PS1.

Por último, apenas construir a coisa no diretório de nível superior:

./configure
make

O bash executável que aparece pode ser mudado para paxsh, mas eu duvido que ele nunca vai se tornar tão prevalente como seu antecessor :-)

E executá-lo, você pode vê-lo em ação:

pax> mv bash paxsh

pax> ./paxsh --version
GNU bash, version 4.2-pax.0(1)-release (i686-pc-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pax> ./paxsh

pax> echo $BASH_VERSION
4.2-pax.0(1)-release

pax> echo "[$PS1]"
[pax> ]

pax> echo "[$(evalps1)]"
[pax> ]

pax> PS1="\h: "

paxbox01: echo "[$PS1]"
[\h: ]

paxbox01: echo "[$(evalps1)]"
[paxbox01: ]

Quando você coloca um dos PSx variáveis na linha de comandos, ecoando $PS1 simplesmente dá-lhe o variável, enquanto o evalps1 comando avalia e produz o resultado.

Agora, concedida, fazendo alterações de código bash para adicionar um comando interno pode ser considerado por alguns a ser um exagero, mas, se você quer uma perfeita avaliação de PS1, certamente uma opção.

Outras dicas

Desde Bash 4.4, você pode usar o @P expansão:

Primeiro eu coloquei sua string rápida em uma variável myprompt usando read -r e um citado aqui-doc:

read -r myprompt <<'EOF'
${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$(__git_ps1 ' (%s)')$ 
EOF

Para imprimir o prompt (como seria interpretado se fosse PS1), use a expansão ${myprompt@P}:

$ printf '%s\n' "${myprompt@P}"
gniourf@rainbow:~$
$

(Na verdade, existem alguns \001 e \002 personagens vindo de \[ e \] que você não pode ver aqui, mas pode vê -los se tentar editar este post; Você também os verá no seu terminal se digitar os comandos).


Para se livrar disso, o truque enviado por Dennis Williamson na lista de discussão é usar read -e -p para que esses personagens sejam interpretados pela Biblioteca Readline:

read -e -p "${myprompt@P}"

Isso levará o usuário, com o myprompt corretamente interpretado.

Para este post, Greg Wooledge respondeu que você também poderia apenas despojar o \001 e \002 da string. Isso pode ser alcançado assim:

myprompt=${myprompt@P}
printf '%s\n' "${myprompt//[$'\001'$'\002']}"

Para este post, Chet Ramey respondeu que você também poderia desligar a edição fora da linha com set +o emacs +o vi. Então isso também será

( set +o emacs +o vi; printf '%s\n' "${myprompt@P}" )

Por que você não apenas processa o $PS1 Escape substitui -se? Uma série de substituições como estas:

p="${PS1//\\u/$USER}"; p="${p//\\h/$HOSTNAME}"

A propósito, o ZSH tem a capacidade de interpretar escapadas rápidas.

print -P '%n@%m %d'

ou

p=${(%%)PS1}

Eu gosto da ideia de consertar o Bash para torná -lo melhor, e aprecio resposta detalhada sobre como remendar a festa. Vou tentar algum dia.

No entanto, sem corrigir o código de origem Bash, tenho um hack de uma linha que é portátil e não duplica a funcionalidade, porque a solução alternativa usa apenas o Bash e seus Buildins.

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1)"; echo "'${x%exit}'"

Observe que há algo estranho acontecendo com tty'areia stdio Ver isso também funciona:

x="$(PS1=\"$PS1\" echo -n | bash --norc -i 2>&1 > /dev/null)"; echo "'${x%exit}'"

Então, embora eu não entenda o que está acontecendo com o stdio Aqui, meu hack está funcionando para mim no Bash 4.2, Nixos GNU/Linux. Pather o código de origem Bash é definitivamente uma solução mais elegante, e deve ser muito fácil e seguro agora que estou usando o NIX.

Duas respostas: "Pure Bash" e "Bash + sed"

Como fazer isso usando sed é mais simples, a primeira resposta usará .

Veja abaixo puro solução.

Expansão imediata, bash + sed

Aí está meu hack:

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
              sed ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"

Explicação:

Corrida bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1

Pode devolver algo como:

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@ubuntu:~$ 
ubuntu@ubuntu:~$ exit

o sed Comando vai então

  • Leve todas as linhas em um buffer (:;$!{N;b};), do que
  • substituir <everything, terminated by end-of-line><prompt>end-of-line<prompt>exit por <prompt>. (s/^\(.*\n\)*\(.*\)\n\2exit$/\2/).
    • Onde <everything, terminated by end-of-line> vir a ser \1
    • e <prompt> vir a ser \2.
Caso de teste:
while ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1 |
          sed ':;$!{N;b};s/^\(.*\n\)*\(.*\)\n\2exit$/\2/p;d')"
    read -rp "$ExpPS1" && [ "$REPLY" != exit ] ;do
    eval "$REPLY"
  done

A partir daí, você está em uma espécie de pseudo -concha interativa (sem instalações de readline, mas isso não importa) ...

ubuntu@ubuntu:~$ cd /tmp
ubuntu@ubuntu:/tmp$ PS1="${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ "
ubuntu@ubuntu:/tmp$ 

(Última linha imprima ambos ubuntu em verde, @, : e $ em preto e caminho (/tmp) Em azul)

ubuntu@ubuntu:/tmp$ exit
ubuntu@ubuntu:/tmp$ od -A n -t c <<< $ExpPS1 
 033   [   1   ;   3   2   m   u   b   u   n   t   u 033   [   0
   m   @ 033   [   1   ;   3   2   m   u   b   u   n   t   u 033
   [   0   m   : 033   [   1   ;   3   4   m   ~ 033   [   0   m
   $  \n

Puro

ExpPS1="$(bash --rcfile <(echo "PS1='$PS1'") -i <<<'' 2>&1)"
ExpPS1_W="${ExpPS1%exit}"
ExpPS1="${ExpPS1_W##*$'\n'}"
ExpPS1_L=${ExpPS1_W%$'\n'$ExpPS1}
while [ "${ExpPS1_W%$'\n'$ExpPS1}" = "$ExpPS1_W" ] ||
      [ "${ExpPS1_L%$'\n'$ExpPS1}" = "$ExpPS1_L" ] ;do
    ExpPS1_P="${ExpPS1_L##*$'\n'}"
    ExpPS1_L=${ExpPS1_L%$'\n'$ExpPS1_P}
    ExpPS1="$ExpPS1_P"$'\n'"$ExpPS1"
  done

o while O loop é necessário para garantir o manuseio correto de avisos de multilina:

Substitua a 1ª linha por:

ExpPS1="$(bash --rcfile <(echo "PS1='${debian_chroot:+($debian_chroot)}\[\e[1;32m\]\u\[\e[0m\]@\[\e[1;32m\]\h\[\e[0m\]:\[\e[1;34m\]\w\[\e[0m\]$ '") -i <<<'' 2>&1)"

ou

ExpPS1="$(bash --rcfile <(echo "PS1='Test string\n$(date)\n$PS1'") -i <<<'' 2>&1)";

O último Multilina Imprimirá:

echo "$ExpPS1"
Test string
Tue May 10 11:04:54 UTC 2016
ubuntu@ubuntu:~$ 

od -A n -t c  <<<${ExpPS1}
   T   e   s   t       s   t   r   i   n   g  \r       T   u   e
       M   a   y       1   0       1   1   :   0   4   :   5   4
       U   T   C       2   0   1   6  \r     033   ]   0   ;   u
   b   u   n   t   u   @   u   b   u   n   t   u   :       ~  \a
   u   b   u   n   t   u   @   u   b   u   n   t   u   :   ~   $
  \n

Você pode ter que escrever um pequeno programa C que use o mesmo código de código (é uma chamada da biblioteca?) Para exibir esse prompt e basta ligar para o programa C. É verdade que isso não é muito portátil, pois você terá que compilá -lo em cada plataforma, mas é uma solução possível.

Mais uma possibilidade:sem editar o código fonte do bash, usando script utilitário (parte do bsdutils pacote no ubuntu):

$ TEST_PS1="\e[31;1m\u@\h:\n\e[0;1m\$ \e[0m"
$ RANDOM_STRING=some_random_string_here_that_is_not_part_of_PS1
$ script /dev/null <<-EOF | awk 'NR==2' RS=$RANDOM_STRING
PS1="$TEST_PS1"; HISTFILE=/dev/null
echo -n $RANDOM_STRING
echo -n $RANDOM_STRING
exit
EOF
<prints the prompt properly here>

script comando gera um arquivo especificado & a de saída é também mostrado no stdout.Se o nome do arquivo for omitido, ele gera um arquivo chamado de transcrição.

Já que não estamos interessados no arquivo de log, neste caso, o nome do arquivo é especificado como /dev/null.Em vez disso, o stdout do comando de script é passado para o awk para processamento adicional.

  1. Todo o código também pode ser encapsulada em uma função.
  2. Além disso, a saída de comandos pode também ser atribuída a uma variável.
  3. Esta abordagem também suporta análise de PROMPT_COMMAND...
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top