2014-12-09 33 views
21

Considerate questo PS1Detect comando vuoto

PS1='\n${_:+$? }$ ' 

Ecco il risultato di alcuni comandi

$ [ 2 = 2 ] 

0 $ [ 2 = 3 ] 

1 $ 

1 $ 

La prima riga non mostra lo stato come previsto, e le due righe successive visualizza corretta uscita codice. Tuttavia, sulla linea 3 è stato premuto solo Enter, quindi vorrei che lo stato andasse via, come la linea 1. Come posso fare questo?

+2

La trappola 'debug' non attiva su una riga di comando vuota, neanche. – chepner

+0

@chepner 'trap 'echo hello' DEBUG' dice' ciao' ogni volta che premo invio. – yellowantphil

+3

Hm, si attiva solo per me su una riga vuota se 'PROMPT_COMMAND' esegue un comando, nel qual caso sembra sparare due volte. (Una volta per il comando vuoto, e una volta per ogni comando eseguito tramite 'PROMPT_COMMAND'.) – chepner

risposta

17

Ecco un divertente, molto semplice possibilità: utilizza la sequenza \# fuga del PS1 insieme con espansioni parametro (e il modo Bash espande sua rapida).

La sequenza di escape \# si espande al numero di comando del comando da eseguire. Questo viene incrementato ogni volta che un comando è stato effettivamente eseguito.Prova:

$ PS1='\# $ ' 
2 $ echo hello 
hello 
3 $ # this is a comment 
3 $ 
3 $ echo hello 
hello 
4 $ 

Ora, ogni volta che una richiesta deve essere visualizzata, Bash espande prima della sequenza di escape trovati nelle PS1, allora (disponibile l'opzione di shell promptvars è impostato, che è il default), questa stringa è ampliato tramite espansione dei parametri, sostituzione dei comandi, espansione aritmetica e rimozione delle virgolette.

Il trucco è quindi di avere una matrice che avrà il k -esimo campo impostato (per la stringa vuota) ogni volta che viene eseguita la (k -1) -esimo comando. Quindi, utilizzando le opportune espansioni dei parametri, saremo in grado di rilevare quando questi campi sono impostati e di visualizzare il codice di ritorno del comando precedente se il campo non è impostato. Se si desidera chiamare questo array __cmdnbary, basta fare:

PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ ' 

sguardo:

$ PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ ' 

0 $ [ 2 = 3 ] 

1 $ 

$ # it seems that it works 

$  echo "it works" 
it works 

0 $ 

Per qualificarsi per la sfida più breve risposta:

PS1='\n${a[\#]-$? }${a[\#]=}$ ' 

che è 31 caratteri.

Non utilizzare questo, ovviamente, come a è un nome troppo banale; inoltre, \$ potrebbe essere migliore di $.


Sembra che non ti piace che la richiesta iniziale è 0 $; si può facilmente modificare questo inizializzare l'array __cmdnbary in modo appropriato: si metterà da qualche parte nel file di configurazione:

__cmdnbary=('' '') # Initialize the field 1! 
PS1='\n${__cmdnbary[\#]-$? }${__cmdnbary[\#]=}\$ ' 
+1

@StevenPenny forse stai cercando un meccanismo per attivare e disattivare il codice di ritorno.Se è il caso, considera questo: 'PS1 = '\ n $ {__ cmdnbary [\ #] - $ {__ hide_prompt_ret- $?}} $ {__ cmdnbary [\ #] =} \ $ ''. Quindi devi solo impostare (sulla stringa vuota) o' disinserire' la variabile '__hide_prompt_ret' per visualizzare il codice di ritorno o nascosto –

+0

Va notato che questo utilizza una quantità crescente di memoria, forse in modo insignificante, ma ancora. – schlimmchen

6

Abbiamo un po 'di tempo per giocare questo fine settimana. Guardando la mia risposta precedente (non buona) e altre risposte penso che questa possa essere probabilmente la risposta più piccola.

Luogo queste righe alla fine del vostro ~/.bash_profile:

PS1='$_ret$ ' 
trapDbg() { 
    local c="$BASH_COMMAND" 
    [[ "$c" != "pc" ]] && export _cmd="$c" 
} 
pc() { 
    local r=$? 
    trap "" DEBUG 
    [[ -n "$_cmd" ]] && _ret="$r " || _ret="" 
    export _ret 
    export _cmd= 
    trap 'trapDbg' DEBUG 
} 
export PROMPT_COMMAND=pc 
trap 'trapDbg' DEBUG 

quindi aprire un nuovo terminale e notare questo comportamento desiderato su BASH prompt dei:

$ uname 
Darwin 
0 $ 
$ 
$ 
$ date 
Sun Dec 14 05:59:03 EST 2014 
0 $ 
$ 
$ [ 1 = 2 ] 
1 $ 
$ 
$ ls 123 
ls: cannot access 123: No such file or directory 
2 $ 
$ 

Spiegazione:

  • Si basa sui ganci trap 'handler' DEBUG e PROMPT_COMMAND.
  • PS1 utilizza una variabile _ret, ad esempio PS1='$_ret$ '.
  • trap Il comando viene eseguito solo quando viene eseguito un comando ma PROMPT_COMMAND viene eseguito anche quando viene premuto un invio vuoto.
  • Il comando trap imposta una variabile _cmd sul comando effettivamente eseguito utilizzando la variabile interna BASH BASH_COMMAND.
  • PROMPT_COMMAND gancio imposta _ret a "$? " se _cmd non è vuota altrimenti imposta _ret a "". Alla fine ripristina _cmd var allo stato vuoto.
+0

No potrebbe esserci qualche altro comando che viene eseguito dopo il tuo ~ ~/.bashrc'. Se non hai '~/.bash_profile' creane uno con solo questo blocco di codice e vedi se questo cambia comportamento. Per eseguire il debug puoi inserire 'echo $ _cmd' subito dopo' trap '"DEBUG;' nella riga 'PROMPT_COMMAND ='. – anubhava

+0

Non vedo '0 $' nei miei test. Puoi eseguirne il debug nel modo che ho suggerito, quindi ottieni il comando 'extra' stampato. Meglio anche testarlo direttamente copia/incolla nel tuo ~ ~ /.bash_profile'. Lo convertirò in una funzione più tardi quando questo funziona per te. – anubhava

+0

Ah questo è il sospetto perché sopra le forme attuali funziona bene. Al momento sono su cellulare, quando tornerò convertirò 'PROMPT_COMMAND' in una funzione' pc' e aggiornerò la risposta. – anubhava

3

Questo non è probabilmente il modo migliore per fare questo, ma sembra funzionare

function pc { 
    foo=$_ 
    fc -l > /tmp/new 
    if cmp -s /tmp/{new,old} || test -z "$foo" 
    then 
    PS1='\n$ ' 
    else 
    PS1='\n$? $ ' 
    fi 
    cp /tmp/{new,old} 
} 
PROMPT_COMMAND=pc 

Risultato

$ [ 2 = 2 ] 

0 $ [ 2 = 3 ] 

1 $ 

$ 
+2

Sembra che tu sia molto interessato alla variabile '_'. Tuttavia, questa variabile si espande fino all'ultimo argomento dell'ultimo comando. Quindi il tuo codice non si comporta come previsto quando quest'ultimo argomento è vuoto, ad es., 'Echo' ''. Inoltre, se si rilascia una riga di commenti, ciò non si comporta come previsto. Inoltre, con il tuo metodo, la ripetizione di un comando non mostrerà il comportamento previsto. –

4

La variabile HISTCMD viene aggiornato ogni volta che viene eseguito un nuovo comando . Sfortunatamente, il valore è mascherato durante l'esecuzione di PROMPT_COMMAND (suppongo per motivi legati al fatto di non avere cronologia incasinata con cose che accadono nel comando prompt). La soluzione che ho trovato è un po 'disordinata, ma sembra che funzioni nei miei test limitati.

# This only works if the prompt has a prefix 
# which is displayed before the status code field. 
# Fortunately, in this case, there is one. 
# Maybe use a no-op prefix in the worst case (!) 
PS1_base=$'\n' 

# Functions for PROMPT_COMMAND 
PS1_update_HISTCMD() { 
    # If HISTCONTROL contains "ignoredups" or "ignoreboth", this breaks. 
    # We should not change it programmatically 
    # (think principle of least astonishment etc) 
    # but we can always gripe. 
    case :$HISTCONTROL: in 
     *:ignoredups:* | *:ignoreboth:*) 
     echo "PS1_update_HISTCMD(): HISTCONTROL contains 'ignoredups' or 'ignoreboth'" >&2 
     echo "PS1_update_HISTCMD(): Warning: Please remove this setting." >&2 ;; 
    esac 
    # PS1_HISTCMD needs to contain the old value of PS1_HISTCMD2 (a copy of HISTCMD) 
    PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD} 
    # PS1_HISTCMD2 needs to be unset for the next prompt to trigger properly 
    unset PS1_HISTCMD2 
} 

PROMPT_COMMAND=PS1_update_HISTCMD 

# Finally, the actual prompt: 
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ ' 

La logica nel prompt è approssimativamente come segue:

${PS1_base#foo...} 

Questo visualizza il prefisso. Il materiale in #... è utile solo per i suoi effetti collaterali.Vogliamo eseguire alcune manipolazioni variabili senza visualizzare i valori delle variabili, quindi le nascondiamo in una sostituzione di stringhe. (Verrà visualizzata cose strane e possibilmente spettacolari se il valore di PS1_base accade mai per cominciare foo seguita dall'indice cronologia dei comandi corrente.)

${PS1_HISTCMD2:=...} 

Questo assegna un valore a PS1_HISTCMD2 (se non è impostata, che abbiamo hanno fatto sì che sia). La sostituzione dovrebbe anche espandersi nominalmente al nuovo valore, ma l'abbiamo nascosto in un ${var#subst} come spiegato sopra.

${HISTCMD%$PS1_HISTCMD} 

Si assegna sia il valore di HISTCMD (quando una nuova voce nella cronologia dei comandi è stato fatto, cioè stiamo eseguendo un nuovo comando) o una stringa vuota (quando il comando è vuota) per PS1_HISTCMD2. Ciò funziona eliminando il valore HISTCMD qualsiasi corrispondenza su PS1_HISTCMD (utilizzando la sintassi di sostituzione del suffisso ${var%subst}).

${_:+...} 

Questo è dalla domanda. Si espanderà a ... qualcosa se il valore di $_ è impostato e non vuoto (che è quando viene eseguito un comando, ma non per esempio se stiamo eseguendo un'assegnazione di variabile). Il "qualcosa" dovrebbe essere il codice di stato (e uno spazio, per la leggibilità) se PS1_HISTCMD2 non è vuoto.

${PS1_HISTCMD2:+$? } 

Là.

'$ ' 

questo è solo il suffisso prompt di vero e proprio, come nel domanda iniziale.

Così le parti principali sono le variabili PS1_HISTCMD, che ricorda il valore precedente di HISTCMD, e la variabile PS1_HISTCMD2 che cattura il valore di HISTCMD modo che possa essere accessibile da dentro PROMPT_COMMAND, ma ha bisogno di essere impostata in PROMPT_COMMAND in modo che il L'assegnazione ${PS1_HISTCMD2:=...} si attiverà di nuovo la volta successiva che viene visualizzato il prompt.

Ho smanettato un po 'cercando di nascondere l'output da ${PS1_HISTCMD2:=...} ma poi mi sono reso conto che in realtà c'è qualcosa che vogliamo mostrare comunque, quindi basta su quello. Non è possibile avere uno PS1_base completamente vuoto perché la shell apparentemente nota e non tenta nemmeno di eseguire una sostituzione quando non c'è alcun valore; ma forse puoi inventare un valore fittizio (una sequenza di fuga senza uscita, forse?) se non hai altro da mostrare. O forse questo potrebbe essere refactored per funzionare con un suffisso invece; ma probabilmente sarà ancora più complicato.

In risposta alla "risposta più piccola" di Anubhava, ecco il codice senza commenti o controllo degli errori.

PS1_base=$'\n' 
PS1_update_HISTCMD() { PS1_HISTCMD=${PS1_HISTCMD2:-$PS1_HISTCMD}; unset PS1_HISTCMD2; } 
PROMPT_COMMAND=PS1_update_HISTCMD 
PS1='${PS1_base#foo${PS1_HISTCMD2:=${HISTCMD%$PS1_HISTCMD}}}${_:+${PS1_HISTCMD2:+$? }}$ ' 
+0

@anubhava Sfida accettata (-: – tripleee

0

ho bisogno di usare grande sceneggiatura bash-preexec.sh.

Anche se non mi piacciono le dipendenze esterne, questa era l'unica cosa che mi aiuta a evitare di avere 1 in $? dopo aver premuto semplicemente enter senza eseguire alcun comando.

Questo va al tuo ~/.bashrc:

__prompt_command() { 
    local exit="$?" 
    PS1='\[email protected]\h: \w \$ ' 
    [ -n "$LASTCMD" -a "$exit" != "0" ] && PS1='['${red}$exit$clear"] $PS1" 
} 
PROMPT_COMMAND=__prompt_command 

[-f ~/.bash-preexec.sh ] && . ~/.bash-preexec.sh 
preexec() { LASTCMD="$1"; }