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.
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á sed.
Veja abaixo puro Bash solução.
Bash 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
.
- Onde
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 Bash
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.
- Todo o código também pode ser encapsulada em uma função.
- Além disso, a saída de comandos pode também ser atribuída a uma variável.
- Esta abordagem também suporta análise de
PROMPT_COMMAND
...