2014-12-09 17 views
21

Rozważmy PS1Detect pusty komenda

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

Tutaj jest wynikiem kilku komend

$ [ 2 = 2 ] 

0 $ [ 2 = 3 ] 

1 $ 

1 $ 

Pierwsza linia pokazuje żadnego statusu zgodnie z oczekiwaniami, a następne dwie linie pokazują prawidłową zjazd kod. Jednak w linii 3 naciśnięto tylko Enter, więc chciałbym, aby status zniknął, podobnie jak wiersz 1. Jak mogę to zrobić?

+2

Opcja 'pułapka DEBUG' nie wyzwala w pustym wierszu poleceń, albo. – chepner

+0

@chepner 'trap 'echo hello' DEBUG' mówi" cześć "za każdym razem, gdy wciskam Enter. – yellowantphil

+3

Hm, uruchamia się tylko dla mnie w pustym wierszu, jeśli 'PROMPT_COMMAND' wykonuje polecenie, w takim przypadku wydaje się, że wystrzelił dwukrotnie. (Raz dla pustego polecenia i raz dla każdego polecenia wykonanego przez 'PROMPT_COMMAND'.) – chepner

Odpowiedz

17

Oto zabawny, bardzo prosta możliwość: wykorzystuje sekwencję \# ucieczki z PS1 wraz z rozszerzeniami parametrów (oraz sposób atakujących rozszerza jego szybka).

Sekwencja wyjściowa \# rozwija się do numeru polecenia, które ma zostać wykonane. Jest to zwiększane za każdym razem, gdy polecenie zostało faktycznie wykonane.Spróbuj go:

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

Teraz, za każdym razem, gdy wiersz ma zostać wyświetlony, atakujących najpierw rozszerza sekwencje znalezione w PS1, a następnie (o ile opcja powłoki promptvars jest ustawiona, które jest domyślne), to ciąg jest rozszerzone poprzez rozszerzenie parametrów, podstawianie poleceń, rozszerzanie arytmetyczne i usuwanie cudzysłowów.

Trick następnie mieć tablicę, która będzie miał K -tym zestaw terenie (ciąg pusty), gdy ( k-1) -tym polecenie jest wykonywane. Następnie, używając odpowiednich rozszerzeń parametrów, będziemy w stanie wykryć, kiedy te pola są ustawione i wyświetlić kod powrotu poprzedniego polecenia, jeśli pole nie jest ustawione. Jeśli chcesz wywołać tę tablicę __cmdnbary, po prostu zrobić:

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

Wygląd:

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

0 $ [ 2 = 3 ] 

1 $ 

$ # it seems that it works 

$  echo "it works" 
it works 

0 $ 

Aby zakwalifikować się do najkrótszej odpowiedzi wyzwanie:

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

to 31 znaków.

Nie używaj tego, oczywiście, ponieważ a to zbyt banalna nazwa; również, \$ może być lepszy niż $.


Wydaje ci się nie podoba, że ​​początkowa jest szybka 0 $; można bardzo łatwo zmodyfikować to poprzez inicjowanie tablicy __cmdnbary odpowiednio: musisz umieścić ten gdzieś w pliku konfiguracyjnym:

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

@StevenPenny może szukasz mechanizmu do włączania i wyłączania kodu powrotu. Jeśli tak, rozważ to: 'PS1 = '\ n $ {__ cmdnbary [\ #] - $ {__ hide_prompt_ret- $?}} $ {__ cmdnbary [\ #] =} \ $ ''. Następnie wystarczy ustawić (do pustego ciągu) lub' unset' zmienną '__hide_prompt_ret', aby pokazywał kod powrotu lub ukryte: –

+0

Należy zauważyć, że wykorzystuje to coraz większą ilość pamięci, może nieznacznie, ale nadal. – schlimmchen

6

Mam trochę czasu na zabawę w ten weekend. Patrząc na moją wcześniejszą odpowiedź (niedobrą) i inne odpowiedzi, myślę, że to prawdopodobnie prawdopodobnie najmniejsza odpowiedź.

Place te linie na końcu swojej ~/.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 

następnie otwórz nowy terminal i odnotować tę pożądanego zachowania na BASH szybka:

$ 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 $ 
$ 

Objaśnienie:

  • Oparte na hakach trap 'handler' DEBUG i PROMPT_COMMAND.
  • używa zmiennej _ret, tj. PS1='$_ret$ '.
  • trap Komenda działa tylko po wykonaniu polecenia, ale PROMPT_COMMAND jest uruchamiane nawet po naciśnięciu pustego klawisza Enter. Polecenie
  • trap ustawia zmienną _cmd na faktycznie wykonaną komendę za pomocą BASH wewnętrznego var BASH_COMMAND.
  • PROMPT_COMMAND Zestawy haków _ret do "$? " jeśli _cmd jest niepusta, w przeciwnym razie ustawia _ret na "". Wreszcie resetuje _cmd var do pustego stanu.
+0

Nie może istnieć inne polecenie, które działa po '~/.bashrc'. Jeśli nie masz "~/.bash_profile", utwórz taki z samym blokiem kodu i zobacz, czy to zmienia zachowanie. Do debugowania możesz wstawić 'echo $ _cmd' tuż po' pułapce "" DEBUG; 'w linii' PROMPT_COMMAND = '. – anubhava

+0

Nie widzę "0 $" w moich testach. Możesz go debugować tak, jak sugerowałem, więc wydrukuj to polecenie 'extra'. Lepiej przetestuj to bezpośrednio kopiuj/wklej do '~ /.bash_profile'. Zamienię to na funkcję później, kiedy to zadziała dla ciebie. – anubhava

+0

Ach, to jest podejrzenie, ponieważ powyższe formularze działają dobrze. Jestem na komórce, kiedy wrócę, przekonwertuję 'PROMPT_COMMAND' na funkcję' pc' i zaktualizuję odpowiedź. – anubhava

3

Prawdopodobnie nie jest to najlepszy sposób, aby to zrobić, ale wydaje się działać

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 

Rezultat

$ [ 2 = 2 ] 

0 $ [ 2 = 3 ] 

1 $ 

$ 
+2

Wydaje się, że bardzo zależy Ci na zmiennej '_'. Jednak ta zmienna rozwija się do ostatniego argumentu ostatniego polecenia. Tak więc twój kod nie zachowuje się zgodnie z oczekiwaniami, gdy ten ostatni argument jest pusty, np. 'Echo '''. Ponadto jeśli wypiszesz wiersz komentarzy, nie zachowuje się to zgodnie z oczekiwaniami. Ponadto powtarzanie polecenia za pomocą tej metody nie spowoduje oczekiwanego zachowania. –

4

Zmienna HISTCMD jest aktualizowana za każdym razem nowa komenda jest wykonywana . Niestety, wartość jest maskowana podczas wykonywania PROMPT_COMMAND (przypuszczam, że z powodów związanych z tym, że historia nie zawodziła z rzeczami, które zdarzają się w poleceniu polecenia). Obejście problemu, które wymyśliłem, jest trochę nieporządne, ale wydaje się działać w moich ograniczonych testach.

# 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:+$? }}$ ' 

Logika w wierszu jest mniej więcej następująco:

${PS1_base#foo...} 

Wyświetla prefiks. Rzeczy w #... są użyteczne tylko ze względu na efekty uboczne.Chcemy wykonać pewne zmienne manipulacje bez wyświetlania wartości zmiennych, więc ukrywamy je w substytucji łańcuchowej. (Spowoduje to wyświetlenie nieparzyste i ewentualnie spektakularne rzeczy, jeżeli wartość PS1_base nie dzieje zacząć foo następnie bieżącego indeksu historii poleceń.)

${PS1_HISTCMD2:=...} 

ta przypisuje wartość do PS1_HISTCMD2 (jeśli nie jest ustawiona, co my upewniłem się, że tak jest). Zastąpienie nominalnie również rozszerzyłoby się do nowej wartości, ale ukryliśmy je w ${var#subst}, jak wyjaśniono powyżej.

${HISTCMD%$PS1_HISTCMD} 

Mamy przypisać albo wartość HISTCMD (gdy nowy wpis w historii poleceń jest dokonywana, czyli realizujemy nową komendę) lub pusty ciąg (gdy polecenie jest pusty) do PS1_HISTCMD2. Działa to poprzez przycięcie wartości HISTCMD dowolnego dopasowania na PS1_HISTCMD (przy użyciu składni zastępczej przyrostka ${var%subst}).

${_:+...} 

To jest pytanie. Rozszerzy się do ... czegoś, jeśli ustawiona jest wartość $_ i jest niepustą (co jest, gdy wykonywane jest polecenie, ale nie na przykład, jeśli wykonujemy przypisanie zmiennych). "Coś" powinno być kodem statusu (i spacji, dla czytelności), jeśli PS1_HISTCMD2 jest niepustą.

${PS1_HISTCMD2:+$? } 

Tam.

'$ ' 

To jest tylko rzeczywisty sufiks polecenia, tak jak w pytaniu oryginalnym.

Więc najważniejsze części są zmienne PS1_HISTCMD który pamięta poprzednią wartość HISTCMD, a zmienna PS1_HISTCMD2 który rejestruje wartość HISTCMD więc może być dostępne od wewnątrz PROMPT_COMMAND, ale musi być wyłączony w PROMPT_COMMAND tak że ${PS1_HISTCMD2:=...} Przypisanie zostanie ponownie uruchomione po następnym wyświetleniu monitu.

Trochę przezwyciężyłem próbę ukrycia wyjścia z ${PS1_HISTCMD2:=...}, ale potem zdałem sobie sprawę, że w rzeczywistości jest coś, co chcemy wyświetlić w każdym razie, więc po prostu na tym pomóż. Nie możesz mieć całkowicie pustego PS1_base, ponieważ powłoka najwyraźniej zauważa i nawet nie próbuje dokonać substytucji, gdy nie ma żadnej wartości; ale może uda ci się wymyślić wartość dummy (może być to sekwencja escape no-op?), jeśli nie masz nic innego, co chcesz wyświetlić. Lub może to być refaktoryzowane do uruchomienia z sufiksem zamiast; ale to prawdopodobnie będzie jeszcze trudniejsze.

W odpowiedzi na "najmniejszą odpowiedź" Anubhawy, oto kod bez komentarzy i sprawdzania błędów.

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 Wyzwanie akceptowane (-: – tripleee

0

muszę korzystać świetny scenariusz bash-preexec.sh.

Chociaż nie lubię zewnętrznych zależności, to było jedyną rzeczą, która pomogła mi uniknąć 1 w $? po prostu naciskając enter bez uruchamiania żadnego polecenia.

Ten idzie do swojej ~/.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"; }