Merge delle history in tempo reale

(pubblicato il 27 gennaio 2021) coding Coding on a computer screen by Markus Spiske is licensed under CC-CC0 1.0

1. Introduzione

Quando si eseguono varie istanze di shell bash (zsh mi pare l'abbia già) nasce l'esigenza di disporre di una history unificata per non correre il rischio che comandi digitati su una certa istanza non siano più disponibili per un riutilizzo futuro.

In alcune configurazioni, le history si sincronizzano quando si chiude esplicitamente la sessione con “exit” o con CTRL-D ma il vantaggio vero sarebbe quello di disporre del merge da subito, senza aspettare di chiudere la sessione. Anche perché, se per errore, l'emulatore di terminale viene chiuso senza prima aver chiuso le sessioni (crash o anche terminando esplicitamente l'applicazione se questo non coincide con il termine della sessione), le history non saranno più disponibili.

Il merge delle history, in sè, è banale. C'è bisogno di un pizzico di magia del comando history stesso e di un minimo di conoscenza della cosidetta “shell”.

Nota a margine: Su Gnu/Linux, tutto ciò è applicabile senza eccezioni. Su Mac OS uso bash 5 fornito da homebrew definendo un nome custom per il file history (HISTFILE in .bashrc) che altrimenti viene resettato impietosamente dopo non più di 500 linee, ogni tot riavvi e dopo tot tempo. Le ho provate tutte per andare oltre questo vincolo (sia modificando .bashrc e .profile sia usando alcune chicche specifiche del Mac che però hanno fallito tutte miseramente) e l'unica cosa che ha funzionato è stato il nome custom.

2. La ricettina

  1. La shell
  2. configurazione history
  3. creazione script

1. La shell (Spiegone iniziale)

In un'istanza di shell i comandi di uno script vengono solitamente eseguiti come sottoprocesso del processo genitore. Uno stesso comando può essere invocato esternamente o essere implementato direttamente dalla shell (incorporato o builtin). I comandi implementati nativamente possono essere più efficienti degli equivalenti esterni perché questi ultimi richiedono la generazione di un processo figlio per essere eseguiti. Inoltre un comando builtin può accedere a parti interne della shell. D'altro canto, l'esecuzione di comandi esterni, col loro meccanismo di sottoprocesso, rende più semplice il ricorso ad un certo grado di parallelismo. Piuttosto che eseguire sequenzialmente n comandi interni, potrei decidere di ricorrere agli equivalenti esterni meno efficienti ma in grado di sfruttare il parallelismo dei sottoprocessi e lasciare al sistema operativo la gestione di questo aspetto.

Il merge delle history viene ottenuto sfruttando questi concetti.

Più facile a farsi che a dirsi:

  1. si scrive una funzione history (che sarà quella invocata dalla shell come se fosse un alias)
  2. nel corpo della funzione history, si usa opportunamente il builtin history che effettuerà il merge vero e proprio
  3. particolare attenzione verrà data all'innesco di questa invocazione

2. Configurazione history

In ogni istanza di shell:

  1. si appende la sessione corrente al file history (history -a)
  2. si cancella la history attualmente in sessione (history -c)
  3. si ripristina la history in sessione dal file history (history -r)
  4. si riscrive history come funzione in modo che possa essere invocata dall'utente in maniera trasparente, eseguendo il sync e richiamando il builtin history (volendo scomodare impropriamente i design pattern, tutto ciò potrebbe ricordare l'adapter per es.)

Queste due funzioni andranno collocate in .bashrc (o .profile o .bash_profile, dipende dalla vostra configurazione) e richiamate attraverso il PROMPT_COMMAND in modo che, prima di ogni visualizzazione del prompt, si esegua il merge come volevamo

3. creazione script

In .bashrc:

...
history(){
  syncHistory
  builtin history "$@"
}
 
syncHistory(){
  builtin history -a
  HISTFILESIZE=$HISTFILESIZE
  builtin history -c
  builtin history -r
}
...
PROMPT_COMMAND=syncHistory

Appendice

terminale vs. shell vs. emulatore di terminale Quando ci si vuole sentire fighi e veri hacker, confessando con un falso senso di colpa e una strizzatina d'occhio, “di lavorare solo aprendo delle shell”, commettiamo due piccole leggerezze:

  1. La shell è un interprete di comandi (bash, dash, ksh, csh, zsh ecc.) e basta. Come può esserlo python per es. Mai sentito dire: “ora apro un bel python”, al di là del senso vagamente inquietante della frase.
  2. Un terminale reale è una console collegata ad uno pseudo-dispositivo tty, collegato a sua volta fisicamente ad una porta seriale.
  3. Un terminale virtuale in ambienti *nix è una console collegata ad uno pseudo-dispositivo tty virtuale (note anche come “console tty”) che permette di eseguire una shell all'interno di una sessione X. Di solito accessibili con CTRL-ALT-Fn (per n=1..7 ma comunque configurabile).
  4. Infine, quando si “apre una shell”, quello che si fa in realtà al 99,9999%, è aprire un emulatore di terminale (xterm, Gnome Terminal, Konsole, Iterm2, PuTTY, ecc.), ossia un terminale virtuale all'interno di un ambiente grafico, su cui eseguire una shell come per i terminali reali/virtuali.

Ulteriore suddivisione può essere operata considerando la tipologia di sessioni aperte e la possibilità o meno di disporre di un accesso al sistema.

Quindi “eseguo” una shell, la “uso”. Ma non la “apro”, pur essendo una “conchiglia”.

#bash #shell