Pregunta

Tengo un script que ejecuta el mismo comando en varios directorios ( fgit ). Para cada directorio, me gustaría que para mostrar la corriente rápida + el comando que se ejecuta allí. ¿Cómo consigo la cadena que corresponde a la PS1 decodificada (ampliado)? Por ejemplo, mi PS1 por defecto es

${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)')$

y me gustaría hacer eco de la username@hostname:/path$ pronta resultante, preferentemente (pero no necesariamente) con los colores agradables. Un rápido vistazo a las instrucciones de Bash no reveló ninguna respuesta definitiva, y echo -e $PS1 sólo evalúa los colores.

¿Fue útil?

Solución

Una gran ventaja del software de código abierto es que la fuente es, así, abierta: -)

Bash en sí no proporciona esta funcionalidad, pero hay varios trucos que puede utilizar para proporcionar un subconjunto (tales como la sustitución de \u con $USER y así sucesivamente). Sin embargo, esto requiere una gran cantidad de duplicación de funcionalidad y asegurar que el código se mantiene en sintonía con lo que hace bash en el futuro.

Si desea obtener todos el poder de las variables rápidas (y no le importa ensuciarse las manos con un poco de codificación (y, si lo hace la mente, ¿por qué estás aquí? )), que es bastante fácil de añadir a la propia cáscara.

Si descarga el código para bash (estoy mirando a la versión 4.2), hay un archivo que contiene y.tab.c la función decode_prompt_string():

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

Esta es la función que evalúa las variables PSx para preguntar. Con el fin de permitir esta funcionalidad que debe proporcionarse a los usuarios del depósito propiamente dicho (y no sólo se utiliza por la cáscara), puede seguir estos pasos para añadir un comando interno evalps1.

En primer lugar, el cambio support/mkversion.sh para que no se confunda con un bash "real", y para que la FSF puede negar todo conocimiento para la garantía :-) simplemente cambiar una línea (añadí el bit -pax):

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

En segundo lugar, el cambio builtins/Makefile.in para añadir un nuevo archivo de origen. Esto implica una serie de pasos.

(a) Añadir $(srcdir)/evalps1.def hasta el final de DEFSRC.

(b) Añadir evalps1.o hasta el final de OFILES.

(c) Añadir las dependencias necesarias:

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

En tercer lugar, añadir el archivo builtins/evalps1.def sí, este es el código que se ejecuta cuando se ejecuta el comando evalps1:

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;
}

La mayor parte de ello es la licencia GPL (ya he modificado desde exit.def) con una función muy simple al final de conseguir y PS1 decodificación.

Por último, acaba de construir la cosa en el directorio de nivel superior:

./configure
make

El ejecutable bash que aparece puede cambiar el nombre a paxsh, aunque dudo que alguna vez llegará a ser tan frecuente como su ancestro: -)

Y ejecutarlo, puede verlo en acción:

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: ]

Cuando se pone una de las variables PSx en el símbolo, $PS1 eco simplemente le da la variable, mientras que el comando evalps1 lo evalúa y envía el resultado.

Ahora, sentado, haciendo cambios en el código de bash para añadir un comando interno puede ser considerado por algunos como una exageración, pero, si desea una evaluación perfecta de PS1, es sin duda una opción.

Otros consejos

Desde Bash 4.4 puede utilizar la expansión @P:

En primer lugar pongo su cadena de petición en una variable usando myprompt read -r y una citado aquí-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 el símbolo (como sería interpretado si fuera PS1), utilice el ${myprompt@P} expansión:

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

(De hecho, hay algunos caracteres \001 y \002, procedente de \[ y \] que no se puede ver aquí, pero se puede ver que si se intenta editar este post, también se verá en su terminal si escribe los comandos).


Para deshacerse de ellos, el truco enviado por Dennis Williamson en la lista de correo fiesta es utilizar read -e -p para que estos personajes se interpretan por la biblioteca readline:

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

Esto hará que el usuario, con el myprompt interpretarse correctamente.

Para este post, Greg woolEdge respondió que puede ser que también acaba de despojar a la \001 y \002 de la cadena. Esto se puede lograr de esta manera:

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

Para este post, Chet Ramey respondió que también se puede desactivar por completo la línea de edición con set +o emacs +o vi. Así que esto va a hacer también:

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

¿Por qué no te procesar las sustituciones $PS1 de escape usted mismo? Una serie de sustituciones, tales como los siguientes:

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

Por cierto, zsh tiene la capacidad de interpretar los escapes rápidos.

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

o

p=${(%%)PS1}

Me gusta la idea de fijar Bash para que sea mejor, y aprecio verbosa respuesta de paxdiablo sobre cómo parchear Bash. Voy a tener un algún movimiento.

Sin embargo, sin parchear Bash código fuente, tengo un corte de una sola línea que es portátil y hace funcionalidad no duplicado, debido a que los usos solución sólo golpe y sus órdenes internas.

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

Tenga en cuenta que hay algo extraño está pasando con tty de stdio y ya que esto también funciona:

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

Así que, aunque no entiendo lo que está pasando con el stdio aquí, mi truco está trabajando para mí en Bash 4.2, NixOS GNU / Linux. Parchear el código fuente Bash es definitivamente una solución más elegante, y que debe ser bastante fácil y segura de hacer ahora que estoy usando Nix.

Dos respuesta: "pura fiesta" y "fiesta + sed"

Como hacer esto mediante el uso de sed es más simple, la primera respuesta utilizará sed .

Vea abajo para solución.

expansión rápida, bash + sed

No es mi truco:

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

Explicación:

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

Puede regresar algo como:

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

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

El comando sed será entonces

  • tome todas las líneas en una memoria intermedia (:;$!{N;b};), que
  • reemplazar <everything, terminated by end-of-line><prompt>end-of-line<prompt>exit por <prompt>. (s/^\(.*\n\)*\(.*\)\n\2exit$/\2/).
    • donde <everything, terminated by end-of-line> \1 convertido
    • y <prompt> convertido \2.
Caso de prueba:
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 de ahí, usted está en una especie de pseudo-shell interactivo (sin instalaciones de readline, pero que de no 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 línea de impresión tanto ubuntu en verde, @, : y $ en negro y la ruta (/tmp) en 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

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

Se requiere que el bucle while para asegurar una manipulación correcta de indicaciones de varias líneas:

reemplazar primera línea 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)"

o

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

La última multilínea 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

Puede que tenga que escribir un pequeño programa en C que utiliza el mismo código hace fiesta (se trata de una llamada a la librería?) Para mostrar que el sistema, y ??simplemente llamar al programa C. Por supuesto, eso no es muy portable ya que tendrá que compilarlo en cada plataforma, pero es una solución posible.

Una posibilidad más: sin necesidad de editar el código fuente fiesta, usando la utilidad script (parte del paquete bsdutils en 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>

comando script genera un archivo especificado y la salida también se muestra en la salida estándar. Si se omite el nombre del archivo, se genera un archivo llamado manuscrito.

Debido a que no está interesado en el archivo de registro en este caso, el nombre de archivo se especifica como /dev/null. En lugar de la salida estándar de la secuencia de comandos se pasa a awk para su posterior procesamiento.

  1. El código completo también se puede encapsular en una función.
  2. Además, la salida rápida también se puede asignar a una variable.
  3. Este enfoque también es compatible con el análisis de PROMPT_COMMAND ...
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top