Domanda

Ho uno script di shell che esegue lo stesso comando in diverse directory ( fgit ). Per ogni directory, vorrei per mostrare il prompt corrente + il comando che sarà eseguito lì. Come faccio ad avere la stringa che corrisponde al decodificato (espanso) PS1? Ad esempio, il mio PS1 di default è

${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 mi piacerebbe fare eco prompt username@hostname:/path$ risultante, preferibilmente (ma non necessariamente) con i bei colori. Un rapido sguardo al manuale Bash non ha rivelato alcuna risposta definitiva, e echo -e $PS1 valuta solo i colori.

È stato utile?

Soluzione

Un grande vantaggio del software open source è che la fonte è, beh, aperto: -)

Bash per sé non fornisce questa funzionalità, ma ci sono diversi trucchi che si possono utilizzare per fornire un sottoinsieme (come ad esempio la sostituzione \u con $USER e così via). Tuttavia, questo richiede un sacco di duplicazione di funzionalità e garantire che il codice è tenuto in sincronia con tutto ciò che fa bash in futuro.

Se si desidera ottenere tutti il potere di variabili pronta (e non vi occupate di sporcarsi le mani con un po 'di codifica (e, se si fa mente, perché sei qui? )), è abbastanza facile da aggiungere alla shell stessa.

Se si scarica il codice per bash (sto guardando versione 4.2), c'è un file y.tab.c, che contiene la funzione decode_prompt_string():

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

Questa è la funzione che valuta le variabili PSx per chiedere conferma. Per consentire questa funzionalità da fornire agli utenti del serbatoio stesso (e non solo usato da il guscio), è possibile seguire questa procedura per aggiungere un comando evalps1 interna.

In primo luogo, il cambiamento support/mkversion.sh in modo che non si confonderlo con un bash "reale", e in modo che la FSF può negare tutta la conoscenza ai fini della garanzia :-) Basta cambiare una riga (ho aggiunto il bit -pax):

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

In secondo luogo, il cambiamento builtins/Makefile.in per aggiungere un nuovo file sorgente. Ciò comporta una serie di passaggi.

(a) Aggiungere $(srcdir)/evalps1.def alla fine del DEFSRC.

(b) Aggiungere evalps1.o alla fine del OFILES.

(c) aggiungere le dipendenze necessarie:

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

In terzo luogo, aggiungere il file builtins/evalps1.def sé, questo è il codice che viene eseguito quando si esegue il 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 massa che è la licenza GPL (poiché modificato da exit.def) con una semplice funzione di fine di ottenere e decodificare PS1.

Infine, solo costruire la cosa nella directory di livello superiore:

./configure
make

L'eseguibile bash che appare possono essere rinominati per paxsh, anche se dubito che sarà mai diventare così diffuso come il suo antenato: -)

E in esecuzione, è possibile vedere in azione:

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 si inserisce una delle variabili PSx nel prompt, facendo eco $PS1 semplicemente ti dà la variabile, mentre il comando evalps1 lo valuta e invia il risultato.

Ora, concesso, facendo modifiche al codice per bash per aggiungere un comando interno può essere considerato da alcuni come eccessivo, ma, se si desidera una valutazione perfetta di PS1, è certamente un'opzione.

Altri suggerimenti

Da Bash 4.4 è possibile utilizzare l'espansione @P:

Per prima cosa ho messo la stringa pronta in un myprompt variabile utilizzando read -r e citato qui-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

Per stampare il prompt (come sarebbe se fosse interpretata PS1), utilizzare il ${myprompt@P} espansione:

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

(In realtà ci sono alcuni personaggi \001 e \002, provenienti da \[ e \] che non si può vedere qui, ma si può vedere loro se si tenta di modificare questo post, dove si può vedere nel vostro terminale se si digitano i comandi).


Per sbarazzarsi di questi, il trucco inviata da Dennis Williamson sulla bash mailing list è quello di utilizzare read -e -p in modo che questi personaggi vengono interpretati dal libreria readline:

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

Questo richiederà all'utente, con il myprompt correttamente interpretato.

Per questo post, Greg Wooledge ha risposto che si potrebbe anche solo striscia la \001 e \002 dalla stringa. Ciò può essere ottenuto in questo modo:

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

Per questo post, Chet Ramey ha risposto che si potrebbe anche spegnere del tutto la linea editing con set +o emacs +o vi. Quindi questo farà anche:

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

Perché non basta elaborare le sostituzioni $PS1 fuga da soli? Una serie di sostituzioni come questi:

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

A proposito, zsh ha la capacità di interpretare escape del prompt.

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

o

p=${(%%)PS1}

Mi piace l'idea di fissare Bash per renderlo migliore, e apprezzo prolissa risposta di paxdiablo su come applicare la patch Bash. Avrò un qualche movimento.

Tuttavia, senza l'applicazione di patch Bash codice sorgente, ho un hack one-liner che sia portatile e non funzionalità non duplicato, perché gli usi soluzione solo Bash e dei suoi comandi incorporati.

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

Si noti che c'è qualcosa di strano sta succedendo con tty di e stdio visto che questo funziona anche:

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

Quindi, anche se non capisco cosa sta succedendo con il stdio qui, il mio trucco sta lavorando per me su Bash 4.2, NixOS GNU / Linux. Patch per il codice sorgente Bash è sicuramente una soluzione più elegante, e dovrebbe essere abbastanza facile e sicuro da fare ora che sto usando Nix.

Due risposta: "bash pura" e "bash + sed"

Come fare questo usando sed è più semplice, la prima risposta utilizzerà .

Vedi sotto per soluzione.

pronta espansione, bash + sed

Non è il mio trucco:

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

Spiegazione:

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

Può restituire qualcosa del genere:

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

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

Il comando sed sarà poi

  • prende tutte le linee in un buffer (:;$!{N;b};), di
  • sostituire <everything, terminated by end-of-line><prompt>end-of-line<prompt>exit da <prompt>. (s/^\(.*\n\)*\(.*\)\n\2exit$/\2/).
    • dove <everything, terminated by end-of-line> \1 diventare
    • e <prompt> \2 diventare.
Test case:
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

Da lì, sei in una sorta di pseudo shell interattiva (senza attrezzature readline, ma questo non ha importanza) ...

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$ 

(Ultima riga di stampa sia ubuntu in verde, @, : e $ in bianco e percorso (/tmp) in blu)

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

E 'richiesto ciclo while di corretta gestione di richieste multilinea:

sostituire prima linea da:

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

L'ultima multilinea stamperà:

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

Potrebbe essere necessario scrivere un piccolo programma C che utilizza lo stesso codice esegue bash (si tratta di una chiamata di libreria?) Per visualizzare quella pronta, e basta chiamare il programma C. Certo, questo non è molto portabile dal momento che dovrete compilare su ogni piattaforma, ma è una possibile soluzione.

Ancora una possibilità: senza modificare il codice sorgente di bash, usando l'utility script (parte del pacchetto bsdutils su 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 file specificato e l'uscita è anche mostrato su stdout. Se nomefile viene omesso, si genera un file chiamato dattiloscritto.

Dato che non sono interessati nel file di registro, in questo caso, il nome del file è specificato come /dev/null. Invece lo stdout del comando script è passato a awk per l'ulteriore elaborazione.

  1. L'intero codice può essere incapsulato in una funzione.
  2. Inoltre, la richiesta può emettere anche essere assegnato ad una variabile.
  3. Questo approccio supporta anche l'analisi di PROMPT_COMMAND ...
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top