Bash fornisce un'altra variabile d'ambiente chiamata PROMPT_COMMAND. Il contenuto di questa variabile viene eseguito come un normale comando Bash appena prima che Bash visualizzi il prompt.
[21:55:01][giles@nikola:~] PS1="[\u@\h:\w]\$ "
[giles@nikola:~] PROMPT_COMMAND="date +%H%M"
2155
[giles@nikola:~] d
bin mail
2156
[giles@nikola:~]
Ciò che è accaduto sopra è che ho cambiato PS1 in
modo da non includere più la sequenza di escape \t
,
così da visualizzare l'ora in un formato che mi piace di
più. Ma l'ora compare in una linea diversa dal
prompt. Aggiustando questo con echo -n ...
(come mostrato
sotto) funziona con Bash 2.0+, ma sembra non funzonare con Bash
1.14.7: apparentemente il prompt viene ottenuto in maniera differente
e il metodo seguente causa una sovrapposizione del testo.
2156
[giles@nikola:~] PROMPT_COMMAND="echo -n [$(date +%H%M)]"
[2156][giles@nikola:~]$
[2156][giles@nikola:~]$ d
bin mail
[2157][giles@nikola:~]$ unset PROMPT_COMMAND
[giles@nikola:~]
echo -n ...
controlla l'output del comando date
e sopprime il
carattere newline finale, permettendo al prompt di apparire tutto su
una riga. Alla fine ho usato il comando unset
per rimuovere
la variabile d'ambiente PROMPT_COMMAND.
Si noti che uso la convenzione $(<comando>) per la sostituzione dei comandi: ovvero:
$(date +%H%M)
significa "sostituisci qui l'output del comando date
+%H%M". Questo funziona in Bash 2.0+. In qualche versione
più vecchia di Bash, precedente alla 1.14.7, potreste dovere
usare i backquote (`date +%H%M`
). I backquote possono essere
usati in Bash 2.0+, ma stanno venendo via via rimpiazzati in favore di
$(), che si annida meglio. Continuerò ad utilizzare questa
convenzione in questo documento. Se state usando una versione
precedente di Bash, potete di solito sostituite dei backquote dove
vedete $(). Se la sostituzione di comandi è preceduta da "\"
(cioè \$(comando) ), usate dei backslash davanti ad
entrambi i backquote (cioè \'comando\' ).
Potete anche usare l'output di normali comandi Linux direttamente nel prompt. Ovviamente, non dovrete inserire molto materiale altrimenti creerà un prompt molto grande. Dovrete anche inserire un comando veloce, perché verrà eseguito ogni volta che il prompt appare sullo schemo e ritardi nell'apparire del prompt mentre state lavorando possono essere molto fastidiosi. (Differentemente dall'esempio precedente, a cui assomiglia molto, questo funziona anche con Bash 1.14.7).
[21:58:33][giles@nikola:~]$ PS1="[\$(date +%H%M)][\u@\h:\w]\$ "
[2159][giles@nikola:~]$ ls
bin mail
[2200][giles@nikola:~]$
È importante notare il backslash prima del segno dollaro della sostituzione di comando. Senza di esso, il comando esterno viene eseguito esattamente una volta: quando la stringa PS1 viene letta nell'ambiente. Per questo prompt, ciò significherebbe mostrare lo stesso orario indipendentemente da quanto il prompt viene utilizzato. Il backslash protegge il contenuto di $() dall'interpretazione immediata della shell, così "date" viene chiamato ogni volta che il prompt viene generato.
Linux viene fornito con molti piccoli programmi di utilità come
date, grep, o wc che consentono di
manipolare informazioni. Se vi trovate a creare complesse combinazioni
di questi programmi all'interno di un prompt, potrebbe essere
più semplice fare voi stessi uno shell script e chiamarlo dal
prompt. Per assicurare che le variabili della shell siano espanse al
momento giusto negli shell script bash sono spesso necessarie delle
sequenze di escape (come visto sopra con il comando date
):
questo viene elevato ad un altro livello all'interno della linea del
prompt PS1 ed evitare ciò creando degli shell script è
una buona idea.
Un esempio di un piccolo shell script usato all'interno di un prompt viene dato a seguire:
#!/bin/bash
# lsbytesum - somma il numero di byte in un elenco di directory
TotalBytes=0
for Bytes in $(ls -l | grep "^-" | cut -c30-41)
do
let TotalBytes=$TotalBytes+$Bytes
done
TotalMeg=$(echo -e "scale=3 \n$TotalBytes/1048576 \nquit" | bc)
echo -n "$TotalMeg"
A volte l'ho usato come una funzione (molto più efficiente - sfortunatamente, spiegare in dettaglio le funzioni va oltre lo scopo di questo documento), altre come uno shell script nella mia directory ~/bin, che è nel mio path. Usata in un prompt:
[2158][giles@nikola:~]$ PS1="[\u@\h:\w (\$(lsbytesum) Mb)]\$ "
[giles@nikola:~ (0 Mb)]$ cd /bin
[giles@nikola:/bin (4.498 Mb)]$
Avrete notato che io metto il nome della macchina, l'ora e la directory corrente nella maggioranza dei miei prompt. Ad eccezione dell'ora queste sono cose molto standard da mettere nel prompt, l'ora è l'aggiunta più comune dopo queste. Ma cosa includere nel prompt è interamente una questione di gusto personale. Questi sono esempi da persone che conosco per aiutare a darvi delle idee.
Il prompt di Dan è minimale ma efficace, particolarmente per via del modo in cui funziona.
[giles@nikola:~]$ cur_tty=$(tty | sed -e "s/.*tty\(.*\)/\1/")
[giles@nikola:~]$ echo $cur_tty
p4
[giles@nikola:~]$ PS1="\!,$cur_tty,\$?\$ "
1095,p4,0$
A Dan non piace avere la directory corrente che può ridimensionare drasticamente il prompt come ci si muove nell'albero delle directory, così ne tiene traccia a mente (o digita "pwd"). Lui ha imparato Unix con csh e tcsh, così usa molto la command history (qualcosa che molti di noi, viziati da bash, non facciamo), così la prima cosa nel prompt è l'history number. La seconda cosa sono i caratteri significativi del tty (l'output di "tty" viene tagliato con sed), una cosa che può essere utile per gli utilizzatori di "screen". La terza cosa è il valore di uscita dell'ultimo comando/pipeline (si noti che questo viene reso inutile da ogni comando eseguito all'interno del prompt - potreste però ottenerlo salvando il valore in una variabile e rimettendolo poi a posto). In fine, lo "\$" è un carattere dollaro per un normale utente e cambia in un cancelletto ("#") se l'utente è root.
Torben Fjerdingstad ha scritto per dirmi che spesso sospende dei job e poi se ne dimentica, così usa il prompt per ricordarsi dei job sospesi:
[giles@nikola:~]$ function jobcount {
> jobs|wc -l| awk '{print $1}'
> }
[giles@nikola:~]$ export PS1='\W[`jobcount`]# '
giles[0]# man ls &
[1] 4150
[1]+ Stopped (tty output) man ls
giles[1]#
Torben usa awk per togliere gli spazi vuoti dall'output di wc, mentre io avrei usato sed oppure tr - non perché siano meglio, ma perché mi sono più familiari. Vi sono probabilmente anche altri modi. Torben delimita la stringa PS1 con apostrofi (single quote), questo evita che Bash interpreti immediatamente i backquote, così lui non deve farli precedere da "\" come ho menzionato.
NOTA: C'è un noto bug in Bash 2.02 che fa sì che il comando jobs (integrato nella shell) non restituisca nulla ad un pipe. Se provate quanto sopra con Bash 2.02, otterrete sempre "0" indipendentemente da quanti job avete sospesi. Chet Ramey, uno dei manutentori di Bash, mi dice che questo sarà corretto in v2.03.
Come menzionato prima, PS1, PS2, PS3, PS4 e PROMPT_COMMAND sono tutti salvati nell'ambiente Bash. Per quelli di noi che hanno precedente esperienza con DOS, l'idea di maneggiare grosse porzioni di codice nell'ambiente è terrificante, perchè l'ambiente DOS era piccolo e proprio non cresceva bene. Vi sono probabilmente limiti pratici su cosa può e cosa dovrebbe essere messo nell'ambiente, ma non so quali siano; stiamo probabilmente parlando di un paio di ordini di grandezza in più di quanto gli utenti DOS siano abituati. Come dice Dan:
"Nella mia shell interattiva ho 62 alias e 25 funzioni. La mia regola generale è che se ho bisogno di qualcosa solamente per uso interattivo e può essere scritta in bash ne faccio una funzione (presumendo che non possa essere espressa facilmente con un alias). Se queste persone hanno problemi di memoria non dovrebbero usare bash. Bash è uno dei programmi più grossi che faccio girare nella mia Linux box (a parte Oracle). Lancia top qualche volta e premi 'M' per ordinare per memoria occupata - vedrai quanto bash è vicino alla cima della lista. Caspita, è più grosso di sendmail! Dì loro di prendere qualcosa come ash".
Credo che quel giorno stesse usando solo la consolle: girando X e le applicazioni X, ho molte cose più grosse di Bash. Ma l'idea è la stessa: l'ambiente è qualcosa da utilizzare senza preoccuparsi di riempirlo troppo.
Rischio la censura da parte dei guru di Unix quando dico questo (per crimine di ipersemplificazione): le funzioni sono in pratica piccoli shell script che vengono caricati nell'ambiente per motivi di efficienza. Citando ancora Dan: "Le funzioni shell sono efficienti quanto più possibile. Approssimativamente è l'equivalente di eseguire uno shell script bash/bourne eccetto che nessun I/O di file è necessario perché la funzione è già in memoria. Le funzioni shell sono tipicamente caricate da .bashrc o .bash_profile a seconda che le si voglia solo nella shell iniziale o anche nelle sottoshell. Se confronti questo con l'esecuzione di uno shell script: la shell fa un fork, il processo figlio apre il file e fa un exec, potenzialmente il path viene esaminato, il kernel apre il file e esamina sufficienti byte da determinare come eseguire il file, nel caso di uno shell script una shell deve essere avviata con il nome dello script come argomento, la shell allora apre il file, legge e esegue i comandi. Diversamente, con una funzione shell, tutto fuorché l'esecuzione dei comandi può essere considerato overhead non necessario".