W Linuksie defaultowy prompt, czyli znak zachęty, oscyluje wokół czegoś na kształt
user @host working_dir #
gdzie oczywiście # wskazuje na konto root’a, zamiast niego zwykły użytkownik ma $. Ale czemu prompt ma nie przekazywać innych użytecznych informacji, a przede wszystki przekazywać ich ładnie?
Mój prompt na tą godzinę (zmiany są niemal natychmiastowe, ale o tym pod koniec) wygląda tak:
Nie to jest ładne, co jest ładne, ale co się komu podoba. Przeanalizujmy jednak taką konfigurację by nauczyć się tworzyć własne. Ale najpierw powiem co jest po kolei bo ciężko się chyba od razu połapać. Kolejno: numer tego polecenia w historii, użytkownik, host, data, stan baterii, temperatura CPU i bieżący katalog. Konfiga pobrać można na dole.
Koncepcja omawianego prompt’a
Kiedy łączę się przez SSH z komórki to ekran jest za mały by pomieścić dłuższe ścieżki, a zdarza sie, że niemający ograniczeń w jej długości extFS, potrafi zapchać linijkę i trochę na zwykły ekranie; dołóżmy jeszcze inne elementy podstawowe – wówczas w ogóle nie widać naszego polecenia – gdzie jest pocżtek, a gdzie koniec. Dlaetgo rozdzieliłem informacje do jednej linii, a tam, gdzie wprowadzamy polecenia są tylko 3 znaki(dla ozdoby 🙂 ).
Wstęp do modyfikacji
Pierwsza sprawa to lokalizacja konfigu. Otóż wystarczy wydać komendę w w rodzaju
PS1="nowy ale nic nie robiący prompt #"
by od razu zmienić wygląd. Ale istnieje przecież plik, który startuje bash’a – .bashrc w katalogu użytkownika lub globalny – /etc/bash.bashrc. Warto zauważyć, że w tym pliku najprawdopodobniej będą co najmniej trzy różne podstawienia do PS1, więc możemy wpisać się w uproszczeniu na końcu pliku.
Kolorki
Pierwszy fragment mojego kodu definiuje od razu najfajniejsze, czyli kolorki opierające się na Escape kodach, czyli zaczynających się od \e[ i służących do sterowania konsolą. Umieszczenie kodów w zmiennych zdecydowanie ułatwi operacje na zmianie kolorów, ale można wykorzystywać ich kombinacje. Spójrzmy na nie:
txtblk='\e[0;30m' # Black - Regular txtred='\e[0;31m' # Red txtgrn='\e[0;32m' # Green txtylw='\e[0;33m' # Yellow txtblu='\e[0;34m' # Blue txtpur='\e[0;35m' # Purple txtcyn='\e[0;36m' # Cyan txtwht='\e[0;37m' # White bakblk='\e[40m' # Black - Background bakred='\e[41m' # Red bakgrn='\e[42m' # Green bakylw='\e[43m' # Yellow bakblu='\e[44m' # Blue bakpur='\e[45m' # Purple bakcyn='\e[46m' # Cyan bakwht='\e[47m' # White txtrst='\e[0m' # Text Reset #przykładowa kombinacja: \e[47; 31m - czerwone na białym tle
Kody jako zmienne, czyli poprzedzone dolarem można umieszczać zarówno w zmiennej PS1, jak i w wejściu dla echo, z tym, że potrzebny będzie wówczas dodatkowy parametr -e.
Elementy interaktywne
Moja konfiguracja obsługuje dowolne elemnty interaktywne, czyli takie, które nie są zczytywane z zmiennych środowiskowych (np. data, czy bieżący katalog), o których opowiem niżej. Może to być jakiekolwiek polecenie. Przykładami są wyświetlanie stanu baterii (bardzo praktyczne w laptopach, ale czemu nie zczytywać stanu zasilacza awaryjnego w serwerowni?) i monitorowanie temperatury.
Zanim przystąpimy do pisania funkcji wykorzystywanych w prompcie należy zwrócić uwagę na pewne modyfikacje w .bashrc. Otóż, gdybyśmy tak po prostu wrzucili funkcje i umieścili do nich odwołania w zmiennych to ich wynik zostałby pobrany tylko raz przy ładowaniu shella. Przecież zmienna ładowa jest tylko raz. Sposobem na ominięcie tego jest przeładowywanie zmiennej za każydym poleceniem. Jest to prostsze niż się może wydawać. Wystarczy definicja funkcji, jej wywołanie, kolejna zmienna i od razu pewne wprowadzenie do innej rzeczy:
function ps1 { #jakiś fajny kod } ps1 PROMPT_COMMAND="LEC=\$?; ps1"
Funkcja ps1 zawiera wszystkie deklaracje prompta, od których oczekujemy, że przeładują się przy każdym kliknięciu Return na klawiaturze. W skrypcie startowym trzeba najpierw pierwszy raz wywołać ładowanie – piąta linijka. Magiczne PROMPT_COMMAND zawiera spis poleceń do wykonania po zakończeniu działania poprzedniej komendy, a więc wynich wyświetli się pomięcy jednym a drugim pustym wierszem w shellu (czyli takim bez polecenia, tylko czysta linia). Oczywiście funkcja ps1 może nazywać się np. foo i nie będzie problemu.
Zostało coś jeszcze, czyć nie? LEC=\$?; ładuje do zmiennej exitcode ostatniego polecenia, ponieważ w trakcie wywołania funkcji może się on nadpisywać przez deklaracje lub inne instrukcje robocze. Wykorzystamy to nieco później.
Funkcja wyświetlająca wraz z ładnym formatowaniem stan baterii pochodzi ze strony http://www.basicallytech.com/blog/index.php?/archives/110-Colour-coded-battery-charge-level-and-status-in-your-bash-prompt.html. Wygląda następująco:
battery_status() { BATTERY=/proc/acpi/battery/BAT0 REM_CAP=`grep "^remaining capacity" $BATTERY/state | awk '{ print $3 }'` FULL_CAP=`grep "^last full capacity" $BATTERY/info | awk '{ print $4 }'` BATSTATE=`grep "^charging state" $BATTERY/state | awk '{ print $3 }'` CHARGE=`echo $(( $REM_CAP * 100 / $FULL_CAP ))` NON='\033[00m' BLD='\033[01m' RED='\033[01;31m' GRN='\033[01;32m' YEL='\033[01;33m' COLOUR="$RED" case "${BATSTATE}" in 'charged') BATSTT="$BLD=$NON" ;; 'charging') BATSTT="$BLD+$NON" ;; 'discharging') BATSTT="$BLD-$NON" ;; esac if [ "$CHARGE" -gt "99" ]; then CHARGE=100 fi if [ "$CHARGE" -gt "15" ]; then COLOUR="$YEL" fi if [ "$CHARGE" -gt "30" ]; then COLOUR="$GRN" fi echo -e "${COLOUR}${CHARGE}%${NON} ${BATSTT}" }
Funkcja wyświetla wynik od razu na ekran więc jej wstawienie do zmiennej będzie wyglądało mniej więcej następująco:
zmienna="napis"`funkcja`"napis"$zmienna;
Oczywiście funkcja wstawiona jest w back-ciapkach (zwanych chyba nieco poprawniej grawisami lub back-tick’ami).
Funkcja wyświetlająca temperaturę (napisana już przeze mnie):
function temperatura { TH_ZONE="Core 0" #konfigurowalne i mandatory! zalezy od acpi TEMP=`sensors | grep "$TH_ZONE" | sed 's/.*:\s*+\(.*\) .*(.*/\1/' | sed 's/.\{4\}$//'` #4 na koncu to .0`C if [ "$TEMP" -le "35" ]; then COLOUR=$bakcyn$txtwht elif [ "$TEMP" -le "55" ]; then COLOUR=$txtcyn elif [ "$TEMP" -le "70" ]; then COLOUR=$txtylw elif [ "$TEMP" -le "80" ]; then COLOUR=$txtpur elif [ "$TEMP" -le "90" ]; then COLOUR=$txtred elif [ "$TEMP" -gt "91" ]; then COLOUR=$bakred$txtblk fi echo -ne $COLOUR$TEMP"°C"; }
Krytyczne jest wprowadzenie zmiennej TH_ZONE – wystarczy odpalić komendę sensors (oczywiście jeśli jej nie ma to trzeba zainstalować) i wybrać nazwę strefy, która nas interesuje. W laptopach pod Linukem bywa słabo z wykrywaniem sterowników, a w netbookach potrafią być w ogóle tylko dwa – CPU + dysk. Funkcja dodatkowo koloruje napis w zależnosci od przedziału – warto poświęcić chwile na dostosowanie zakresów, gdyż jedne komputery nie wytrzymują i się wyłączają 85, a inne (głównie netbooki) trzymają teoretycznie do 115 (co ciekawe jeden taki sprzęt zagrzałem do 105 – przeżył i obyło się bez swądu palonej elektroniki).
Porada: pierwsza granica to zazwyczaj temperatura zmierzona po zimnym rozruchu po odstaniu godziny.
Zmienne środowiskowe i zlepianie wszystkiego do kupy
Teraz ciało funkcji ps1. Zacznujmy od czyszczenia na wszelki wypadek zmiennej (jako, że wszędzie dopisujemu, a nie nadpisujemy):
PS1="" #reset
Pierwszym elementem, który się pojawia (dla tty: albo i nie) to zielony tick po pomyślnym wywołaniu komendy poprzedniej wstawiony w border’a lub cała linijka z czerwonym cross’em i podanym exitcodem. Użyteczne, bo programy nie zawsze podają dokładny kod błędu.
if [[ $LEC == 0 ]]; then PS1=$PS1"\[\033[01;32m\]\342\234\223\e[0m─" else PS1=$PS1"\[\033[01;31m\]\342\234\227\e[0m [exitcode: $LEC]\n┌─" fi
Mało czytelnie? $LEC to wcześniej opisany kod wyjścia (normalnie $?) ostatniej komendy (realnie, a nie tej ze skryptu), natomiast \342\234\223 oraz \342\234\227 to ptaszek i iks w UTF’ie (┌─ odpowiada za „zakręt” ramki zcalającej obie linie prompta).
Warto wiedzieć na kogo się zalogowaliśmy lub jakie konto ktoś zostawił:
red="\[\e[0;33m\]" yellow="\[\e[0;31m\]" if [ `id -u` -eq "0" ]; then root="${yellow}" else root="${red}" fi PS1=$PS1"[!\!]─[${root}\u\[\e[0;37m\]]─"
Ostatnia linia dopisuje kolorki z if’a powyżej, nawiasy i właściwą nazwę użytkownika – parametr \u.
Kolejne informacje – host \h i data (\d) wraz z czasem (\t):
PS1=$PS1"[\[\e[0;96m\]\h\[\e[0;37m\]]─[\e[0m\e[0;33m\d \t\e[0m]─"
Doklejmy teraz informacje z funkcji. Dodatkowo stan baterii wyświetlany jest tylko, gdy nie ładujemy (ale zmiana jest bardzo prosta – to pierwszy if):
#bateria if grep --quiet off-line /proc/acpi/ac_adapter/AC/state; then PS1=$PS1"[\[\e[0;35m\]"`battery_status`"\[\e[0;37m\]]─" fi #temperatura PS1=$PS1"[\[\e[0;35m\]"`temperatura`"\[\e[0;37m\]]─"
Na koniec jedynie katalog roboczy (\w) i zawinięcie linijki. Deklaracja drugiej linii w prompcie to PS2:
PS1=$PS1"[\[\e[0;32m\]\w\[\e[0;37m\]]\n\[\e[0;37m\]└──| \[\e[0m\]" PS2="╾──| "
Dalsze możliwości
Wymienione kolory to jedynie podstawowe kombinacje – na buforrze ramki, ale i na emulatorach można pokusić się o kolory w liczbie 256 (https://wiki.archlinux.org/index.php/Color_Bash_Prompt#Load.2FMem_Status_for_256colors), lub dodać którąś ze zmiennych:
\D{format} format daty zdeklarowany notacji strftime(3) umieszczonyw w klamrach \d data \t czas 24h \h nazwa hosta do pierwszej kropki (bez sieci) \H nazwa hosta \j liczba obecnych zadań w konsoli \u nazwa uzytkownika \v wersja powłoki \w katalog roboczy \W katalog roboczy ze skróconym katalogiem domowym do ~ \! numer tego polecenia w historii (użyteczne jeśli mamy spory bufor ekranu i widzimy co najmniej kilka komend, a nam nie chce się bawić strzałką w górę; można też zapamiętać numerek zamiast bardzo długiej komendy i użyć później \$ dolar dla użytkowników i # dla super-użytkownika \\ anulowyany (czyli drukowalny) backslash
Więcej na https://wiki.archlinux.org/index.php/Color_Bash_Prompt#Prompt_escapes.
Dalszy rozwój
Prompt będzie zapewne ewoluował gdy zajdzie taka potrzeba – liczba zalogowanych użytkowników, wolne miejsce na dysku, liczba nowych maili, status usług, czy bezpieczeństwa (np. wykrzyknik dla alertów, daszek dla przeciążeia itp.), prędkość wiatraczka lub losowa linia z pliku z cytatami Torvaldsa… Cokolwiek co, okaże się użytczne i odczytywalne przez basha jako string może być łatwo dodane w nawiasy kwadratowe i cieszyć oko z dobrego wykorzystania potencjału shella.
Dobre źródła informacji: