Question

J'ai un script shell qui exécute la même commande dans plusieurs répertoires ( fgit ). Pour chaque répertoire, je voudrais montrer à la + rapide en cours de la commande qui sera exécutée là. Comment puis-je obtenir la chaîne qui correspond à la décodée (élargi) PS1? Par exemple, mon PS1 par défaut est

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

et je voudrais faire écho à l'username@hostname:/path$ rapide résultant, de préférence (mais pas nécessairement) avec les couleurs agréables. Un coup d'œil sur le manuel Bash n'a révélé aucune réponse définitive, et echo -e $PS1 évalue uniquement les couleurs.

Était-ce utile?

La solution

Un grand avantage de logiciels open source est que la source est, bien, ouvert: -)

Bash lui-même ne fournit pas cette fonctionnalité, mais il y a plusieurs astuces que vous pouvez utiliser pour fournir un sous-ensemble (par exemple en substituant \u avec $USER et ainsi de suite). Cependant, cela nécessite beaucoup de duplication de la fonctionnalité et veiller à ce que le code est maintenu en phase avec ce bash fait à l'avenir.

Si vous voulez obtenir tous la puissance des variables rapides (et ne vous dérange pas se salir les mains avec un peu de codage (et, si vous le permettez, pourquoi êtes-vous ici? )), il est assez facile d'ajouter à la coque elle-même.

Si vous téléchargez le code de bash (je regarde la version 4.2), il y a un fichier qui contient y.tab.c la fonction decode_prompt_string():

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

Ceci est la fonction qui permet d'évaluer les variables PSx pour demander confirmation. Afin de permettre cette fonctionnalité à fournir aux utilisateurs de la coque elle-même (plutôt que de simplement utiliser par la coquille), vous pouvez suivre ces étapes pour ajouter une evalps1 de commande interne.

Tout d'abord, le changement support/mkversion.sh de sorte que vous ne le confondre avec un « vrai » bash, et que la FSF ne peut nier toutes les connaissances à des fins de garantie :-) changer simplement une ligne (j'ai ajouté le bit -pax):

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

En second lieu, le changement builtins/Makefile.in pour ajouter un nouveau fichier source. Cela implique un certain nombre d'étapes.

(a) Ajouter $(srcdir)/evalps1.def à la fin de DEFSRC.

(b) Ajouter evalps1.o à la fin de OFILES.

(c) Ajouter les dépendances requises:

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

En troisième lieu, ajouter le fichier builtins/evalps1.def lui-même, c'est le code qui est exécuté lorsque vous exécutez la commande 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 plus grande partie de c'est la licence GPL (depuis que je l'ai modifié de exit.def) avec une fonction très simple à la fin pour obtenir et décoder PS1.

Enfin, juste construire la chose dans le répertoire de niveau supérieur:

./configure
make

L'exécutable bash qui apparaît peut être rebaptisés à paxsh, mais je doute qu'il ne deviendra jamais aussi répandue que son ancêtre: -)

Et en cours d'exécution, vous pouvez le voir en action:

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

Lorsque vous mettez l'une des variables de PSx dans l'invite, en écho $PS1 vous donne simplement la variable, alors que la commande evalps1 évalue et renvoie le résultat.

Maintenant, d'accord, ce qui rend les changements de code à bash pour ajouter une commande interne peut être considéré par certains comme surpuissant mais si vous voulez une évaluation parfaite de PS1, il est certainement une option.

Autres conseils

Depuis Bash 4.4, vous pouvez utiliser l'extension de @P:

D'abord, je mets votre chaîne d'invite dans une myprompt variable en utilisant read -r et cité ici-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

Pour imprimer l'invite (comme il serait interprété si elle était PS1), utilisez l'extension ${myprompt@P}:

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

(En fait, il y a quelques caractères \001 et \002, venant de \[ et \] que vous ne pouvez pas voir ici, mais vous pouvez les voir si vous essayez de modifier ce poste, vous les verrez également dans votre terminal si vous tapez les commandes).


Pour se débarrasser de ces derniers, l'astuce envoyée par Dennis Williamson sur la liste de diffusion de bash est d'utiliser read -e -p pour que ces personnages s'interprétés par la bibliothèque readline:

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

Cette invite l'utilisateur, avec l'myprompt interprété correctement.

Pour ce poste, Greg Wooledge a répondu que vous pourriez aussi bien juste la bande \001 et \002 de la chaîne. Ceci peut être réalisé comme ceci:

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

Pour ce poste, Chet Ramey a répondu que vous pouvez également désactiver la ligne d'édition tout à fait avec set +o emacs +o vi. Donc, cela va faire aussi:

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

Pourquoi ne pas traiter seulement les substitutions d'échappement $PS1 vous-même? Une série de substitutions telles que:

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

Par ailleurs, zsh a la capacité d'interpréter les évasions rapides.

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

ou

p=${(%%)PS1}

Je aime l'idée de fixer Bash pour faire mieux, et je vous remercie bavard de paxdiablo réponse sur la façon de patcher Bash. Je vais prendre un certain temps d'aller.

Cependant, sans patcher le code source Bash, j'ai un hack d'une doublure qui est à la fois portable et ne pas faire double emploi fonctionnalité, car les utilisations de contournement que Bash et ses fonctions intégrées.

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

Notez qu'il ya quelque chose d'étrange de voir et de tty stdio que cela fonctionne aussi:

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

Alors, bien que je ne comprends pas ce qui se passe avec le stdio ici, mon bidouille travaille pour moi sur Bash 4.2, Nixos GNU / Linux. Patcher le code source Bash est certainement une solution plus élégante, et il devrait être assez sûr et facile à faire maintenant que j'utilise Nix.

Deux réponse: "bash pur" et "bash + sed"

Comme le faire en utilisant sed est plus simple, la première réponse utilisera solution.

extension rapide, bash + sed

Il est mon bidouille:

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

Explication:

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

Peut retourner quelque chose comme:

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

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

La commande sera alors sed

  • prendre toutes les lignes dans une mémoire tampon (:;$!{N;b};), que
  • remplacer <everything, terminated by end-of-line><prompt>end-of-line<prompt>exit par <prompt>. (s/^\(.*\n\)*\(.*\)\n\2exit$/\2/).
    • où se <everything, terminated by end-of-line> \1
    • et <prompt> se \2.
Cas de test:
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 là, vous êtes dans une sorte de pseudo shell interactif (sans installations readline, mais qui est peu importe) ...

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$ 

(Dernière ligne imprimer à la fois ubuntu en vert, @, : et $ en noir et le chemin (/tmp) en bleu)

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

La boucle de while est nécessaire pour assurer une manipulation correcte des invites multilignes:

remplacer 1ère ligne par:

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

Le dernier multiligne imprimer:

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

Vous devrez peut-être écrire un petit programme C qui utilise le même code bash (est-il un appel bibliothèque?) Pour afficher cette invite, et il suffit d'appeler le programme C. Certes, ce n'est pas très portable puisque vous devrez compiler sur chaque plate-forme, mais il est une solution possible.

Une autre possibilité: sans modifier le code source de bash, à l'aide de l'utilitaire script (partie du paquet bsdutils sur 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 commande génère un fichier spécifié et la sortie est également représentée sur la sortie standard. Si le nom de fichier est omis, il génère un fichier appelé tapuscrit.

Étant donné que nous ne sommes pas intéressés par le fichier journal dans ce cas, le nom de fichier est spécifié comme /dev/null. Au lieu de cela stdout de la commande de script est passé à awk pour un traitement ultérieur.

  1. Le code entier peut également être encapsulé dans une fonction.
  2. En outre, l'invite sortie peut également être affectée à une variable.
  3. Cette approche prend également en charge l'analyse de PROMPT_COMMAND ...
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top