L’inizializzazione della shell (bash)

Per molto tempo non ho avuto una buona comprensione dei file di inizializzazione della shell, dato che è un argomento un po’ spinoso. Ora ho fatto chiarezza, e riporto qui quello che ho imparato. In questo articolo, quando parlo di shell, faccio sempre riferimento a Bash, la shell più diffusa e utilizzata. Il comportamento delle altre shell potrebbe essere diverso.

Login e non-login shell

Per prima cosa occorre fare distinzione tra login shell e non-login shell. La prima è una shell su cui l’utente effettua il login. Il login può avvinire con le classiche credenziali, oppure con altri sistemi, ad esempio ssh può utilizzare la chiave pubblica dell’utente. In ogni caso la shell che viene lanciata è detta “shell di login”.

Il processo di una login shell può essere riconosciuto perché solitamente il nome dell’eseguibile è preceduto da un trattino:

$ ps -ef | grep bash
utente 18928 18925  0 14:46 pts/1    00:00:00 /bin/bash
utente 19917 16915  0 15:05 tty1     00:00:00 -bash

In questo esempio la prima è una shell non-login, la seconda è una shell login.

Le shell di non-login, invece non richiedono autenticazione. L’esempio più semplice si ha lanciando una seconda shell (anche detta sub-shell) in una shell:

$ bash
$

Apparentemente non accade nulla, in realtà è stato creato un processo figlio per la sub-shell, che non ha chiesto il login perché lo ha “ereditato” dal processo padre. Se digitiamo “exit” usciamo dalla sub-shell e torniamo nella shell originale.

Inizializzazione della shell di login

Questa distinzione è importante perché la shell si inizializza in modo diverso a seconda che sia di login o non-login. Una shell di login, quando si avvia, esegue le istruzioni contenute nel file /etc/profile. Questo file è quindi utilizzato per le shell di tutti gli utenti, ed è gestito dall’amministratore di sistema.

Successivamente la shell di login va a cercare un file nella home dell’utente, per eseguire le istruzioni specificate dal singolo utente, che possono eventualmente sovrascrivere quelle di /etc/profile. Qui c’è una piccola complicazione: la shell cerca nell’ordine, nella home dell’utente, questi tre file (notare il punto iniziale):

.bash_profile
.bash_login
.profile

ed esegue solo il primo che trova, ignorando gli altri. Quindi inizia cercando .bash_profile. Se esiste lo esegue e ignora gli altri due. Altrimenti cerca .bash_login. Se esiste lo esegue e ignora .profile, altrimenti va avanti con l’ultimo. Ovviamente l’utente può decidere di non creare nessuno dei tre file, e quindi la shell non eseguirà nessuna istruzione. Solitamente per questo file si utilizza il primo nome: .bash_profile.

Inizializzazione della shell di non-login

Passiamo adesso al comportamente della shell avviata come non-login, che è speculare a quello della login-shell. All’avvio per prima cosa esegue le istruzioni contenute in /etc/bash.bashrc. Poi va a cercare nella home dell’utente il file .bashrc, e se esiste ne esegue le istruzioni contenute.

Semplifichiamo

Ricapitolando questi sono i file utilizzati per l’inizializzazione:

login shell:
/etc/profile
~/.bash_profile

non-login shell:
/etc/bash.bashrc
~/.bashrc

Tuttavia nella maggior parte dei casi non è particolarmente utile avere file separati in base al tipo di shell. Per questo solitamente le moderne distribuzioni semplificano il tutto facendo eseguire ad ogni “profile” il corrispettivo “bashrc”:

/etc/profile ----chiama----> /etc/bash.bashrc
~/.bash_profile ---chiama--> ~/.bashrc

Il risultato è che tutte le shell eseguiranno i “bashrc”, e in più quelle di login eseguiranno anche i “profile”. Quindi, dal punto di vista dell’utente, il file da prendere in considerazione è solo .bashrc.

Shell interattive e non interattive

Una seconda distinzione importante è tra shell interattiva e non interattiva. Una shell è interattiva quando è “agganciata” ad un terminale (esempio tastiera e monitor). Se all’avvio la shell riconosce questa caratteristica, imposta con un default la variabile PS1 (che serve per mostrare il prompt), e appende nella variabile $- il carattere “i”, per indicare che è “interactive”.

Solitamente si lavora sempre in una shell interattiva. Le shell non interattive sono utilizzate ad esempio per eseguire gli script. Quando si lancia uno script:

$ ./script.sh

questo non viene eseguito dalla shell in cui ci si trova, ma essa lancia una sub-shell che effettivamente esegue lo script, e che termina con il termine dello script. Questa sub-shell non è interattiva. Uno script (e quindi una shell non interattiva) può anche essere eseguito da un qualsiasi processo.

Dato il largo contesto in cui può essere utilizzata una shell non interattiva, queste shell non si inizializzano in nessun modo, per non correre il rischio di avere un conflitto tra l’inizializzazione della shell e quello che si aspetta di trovare il processo che la lancia. Naturalmente questo non toglie che la nuova shell erediterà, come accade di norma, tutto l’ambiente del processo padre, comprese le variabili d’ambiente (PATH, PWD, etc…).

Una nota su source

Una nota utile riguarda i permessi dei file di inizializzazione. A differenza di quanto si possa credere, questi non hanno bisogno del permesso di esecuzione. La spiegazione è semplice.

Come detto in precedenza, quando si esegue uno script viene creata una sub-shell apposita, il cui ambiente è una copia dell’ambiente della shell originale. Se poi lo script modifica l’ambiente, ad esempio settando una variabile, verrà modificato l’ambiente della sub-shell, senza toccare quello della shell padre. Quando lo script terminerà, la sub-shell sarà distrutta con il suo ambiente, e non avrà modificato l’ambiente del padre. Quindi è impossibile “inizializzare” l’ambiente di una shell lanciando uno script!

Per fare ciò è invece necessario utilizzare il comando source (o l’equivalente “.”):

$ source /etc/profile
$ . /etc/profile #equivalente, ma posix-compliant

che dice alla shell di eseguire direttamente le istruzioni dello script, modificando così il suo ambiente. E’ quindi chiaro che quando si utilizza source, non è necessario che il file sia eseguibile.

Logout

Inifine può essere utile sapere che quando la shell esegue il logout esegue le istruzioni di questi due file:

/etc/bash.bash_logout
~/.bash_logout