Mettiamo che – come nel mio caso – state scrivendo uno script (magari proprio in bash, ma – perché no… – anche in altri linguaggi) che ha bisogno che sul sistema siano correttamente installate determinate dipendenze, utili alla sua esecuzione .
Mettiamo che – come nel mio caso – avete previsto uno script bash che si occupi dell’installazione dello script (qualcosa del tipo install.sh, che crea, sposta, modifica alcuni file) e che vogliate che questo si occupi anche dell’installazione delle suddette dipendenze.

Si può fare? Sì, si può fare. Vi spiego come.
Analizziamo innanzitutto i passaggi che si dovrebbero svolgere, ordinandoli:

  1. vengono elencate (ad esempio, tramite un array) le dipendenze necessarie;
  2. ricerca di un gestore di pacchetti valido;
  3. verifica delle dipendenze mancanti nel sistema in uso, elencando anche queste (un nuovo array);
  4. ricerca di un gestore di pacchetti valido;
  5. installazione delle dipendenze segnate come mancanti.

Prima osservazione, più che lecita: i punti 2) e 4) non fanno la stessa cosa? A che serve ripetere due volte la stessa operazione?
Risposta: serve ripetere la stessa operazione perché sì, nonostante facciano la stessa cosa (ricerca un gestore di pacchetti valido), la ricerca, nei due casi, può e anzi dovrebbe portare a risultati diversi; questo perché bisogna tenere in conto che il gestore selezionato al punto 2) deve svolgere l’operazione indicata come 3), mentre il gestore selezionato al punto 4) svolgerà il compito 5).
E non è detto che per entrambe le operazioni sia opportuno utilizzare lo stesso gestore (anche se è cosa possibile), anzi…

Un esempio concreto chiarificherà questa spiegazione: visto che il gestore selezionato al punto 4) dovrà procedere all’installazione di alcuni pacchetti – come indicato al punto 5) – se la distribuzione è openSuse si utilizzerà zypper, se è Mandriva si utilizzerà urpmi, se è Fedora si utilizzerà yum. Mentre invece il punto 3), in tutti questi tre casi (openSuse, Mandriva e Fedora) può essere sempre svolto da rpm. Che per giunta svolge il compito 3) meglio degli altri già elencati; al contrario, non sarebbe invece molto indicato per svolgere il punto 5), per motivi che tutti conosciamo.
Lo stesso dicasi per altre distribuzioni. Ad esempio, per Debian, Ubuntu e derivate varie, il punto 3) verrà svolto da dpkg, il punto 5) da apt-get.

Seconda osservazione: è proprio necessario verificare quali dipendenze sono mancanti? Non si fa prima a proporre l’installazione di tutte le dipendenze, così che sia poi il gestore a occuparsi di installare solo quelle mancanti e eventualmente a segnalare quelle già presenti? Sì, certo, si può fare e sicuramente è più comodo e veloce. Ma qui si valuterà il processo “più complesso”, negli altri casi non dovrebbe essere difficile (per voi, per chi legge, per chi è interessato) snellire la procedura, a seconda delle proprie esigenze.
Si tenga anche in conto che lo script potrebbe fallire, per tanti motivi (perché non è stato trovato un gestore di pacchetti adatto, cosa possibile perché non si possono considerare tutte le distribuzioni Linux esistenti, con i loro relativi gestori): in questo caso, potremo indicare all’utente quali dipendenze sono appunto mancanti, e invitarlo a installarle manualmente. Cosa che è molto più elegante, in questo caso, dell’indicare tutte le dipendenze necessarie, installate o meno, mettendo così l’utente nella condizione di dover controllare quali deve provvedere a installare manualmente.

Terza osservazione: l’installazione, ovvero il passaggio 5), avviene del tutto automaticamente, cioè senza nessun interazione con l’utente? No, volutamente no. Lo script che propongo lancia il gestore con il corretto comando d’installazione: è facile e molto probabile che poi – qualsiasi sia la distribuzione, qualsiasi sia il gestore selezionato – verrà chiesto all’utente di confermare.
È una scelta voluta, perché esperienza mi ha insegnato che non è corretto installare software senza renderne consapevole e partecipe l’utente e che non è corretto installare senza aver prima visionato quali cambiamenti verranno apportati al sistema (in alcuni casi, l’installazione di alcuni pacchetti, per via di errati calcoli o problemi con le dipendenze che si portano dietro, possono rendere un sistema instabile…).

Finite le osservazioni, passiamo al codice. È innanzitutto necessario stabilire quali sono le dipendenze necessarie. Vorrei subito chiarire, a scanso di equivoci, che:

  1. quando parlo di “dipendenze” o di “dipendenze necessarie” mi riferisco alle dipendenze richieste dal nostro script (affinché funzioni correttamente);
  2. quando parlo di “dipendenze mancanti” mi riferisco alle dipendenze del nostro script e che mancano sul sistema in uso (è quindi questo un sotto-insieme o una selezione del precedente).

Avevamo previsto un array, che mi sembra la scelta più opportuna:

DEPENDENCIES=(adduser curlftpfs dialog inotify-tools libnotify-bin nfs-common)

Questo soddisfa il punto 1) nella lista dei passaggi da compiere. Passiamo ai punti 2) e 3), ovvero alla selezione di un opportuno gestore di pacchetti e alla verifica di quali dipendenze sono mancanti:

# What dependencies are missing?
PKGSTOINSTALL=""
for (( i=0; i<${tLen=${#DEPENDENCIES[@]}}; i++ )); do
	# Debian, Ubuntu and derivatives (with dpkg)
	if which dpkg &> /dev/null; then
		if [[ ! `dpkg -l | grep -w "ii  ${DEPENDENCIES[$i]} "` ]]; then
			PKGSTOINSTALL=$PKGSTOINSTALL" "${DEPENDENCIES[$i]}
		fi
	# OpenSuse, Mandriva, Fedora, CentOs, ecc. (with rpm)
	elif which rpm &> /dev/null; then
		if [[ ! `rpm -q ${DEPENDENCIES[$i]}` ]]; then
			PKGSTOINSTALL=$PKGSTOINSTALL" "${DEPENDENCIES[$i]}
		fi
	# ArchLinux (with pacman)
	elif which pacman &> /dev/null; then
		if [[ ! `pacman -Qqe | grep "${DEPENDENCIES[$i]}"` ]]; then
			PKGSTOINSTALL=$PKGSTOINSTALL" "${DEPENDENCIES[$i]}
		fi
	# If it's impossible to determine if there are missing dependencies, mark all as missing
	else
		PKGSTOINSTALL=$PKGSTOINSTALL" "${DEPENDENCIES[$i]}
	fi
done

Nonostante reputi questo codice abbastanza comprensibile, vado a commentare:

  1. innanzitutto vado a creare la variabile (vuota) PKGSTOINSTALL, che conterrà l’elenco (non è un array, ma una stringa, con gli spazi a mò di divisore) delle dipendenze mancanti;
  2. viene quindi eseguito un ciclo (riga 3) per ognuna delle dipendenze da verificare;
  3. all’interno del ciclo, tramite un (complesso) costrutto if-elif-else, vado a verificare se è disponibile un gestore di pacchetti, avvalendomi di which (che stampa la localizzazione di un eseguibile, quando è presente, altrimenti nulla) e, se disponibile, quale (righe 5, 10 e 15).
    Qui, in particolare, ricerchiamo tra dpkg (per Debian, Ubuntu e derivate, come Mint), rpm (per OpenSuse, Mandriva, Fedora, CentOs e altre) e pacman (per ArchLinux), che dovrebbero coprire la stragrande maggioranza delle distribuzioni, sicuramente tutte quelle più diffuse;
  4. viene quindi verificata se la singola dipendenza (che stiamo ciclando) è già installata o meno, eseguendo un comando specifico per il gestore che abbiamo selezionato (righe 6, 11 e 16);
  5. se la dipendenza non risulta installata, viene accodata alla stringa PKGSTOINSTALL (righe 7, 12 e 17), preceduta da uno spazio. Se invece è già installata, passa alla successiva dipendenza da controllare;
  6. se non viene trovato un gestore utile (alternativo quindi a quanto descritto al punto 3) di questo elenco), allora segna automaticamente la dipendenza come mancante (riga 21), segnando di fatto tutte le dipendenze come mancanti (perché, ovviamente, anche la successiva iterazione del ciclo porterà allo stesso risultato…).

Alla fine di questo blocco di codice – come per altro già accennato – la variabile PKGSTOINSTALL conterrà quindi l’elenco delle dipendenze mancanti (e quindi da installare!), separate l’un l’altra da uno spazio (come tutti i gestori si aspettano siano passato l’elenco).

Si noti che questo codice non è “perfetto”: ad ogni ciclo, di fatti, si va a rieseguire il controllo del gestore. Non dovrebbe portare a chissà quali performance negative e/o penalizzanti, ma non escludo assolutamente che si possano scrivere numerosi varianti migliori di questa e che portino allo stesso risultato. Anzi, se ne avete voglia, prendetelo come un esercizio, visto che sicuramente si può fare (e si dovrebbe fare…) di meglio.

Passiamo al codice che si occupa dell’installazione effettiva:

# If some dependencies are missing, asks if user wants to install
if [ "$PKGSTOINSTALL" != "" ]; then
	echo -n "Some dependencies are missing. Want to install them? (Y/n): "
	read SURE
	# If user want to install missing dependencies
	if [[ $SURE = "Y" || $SURE = "y" || $SURE = "" ]]; then
		# Debian, Ubuntu and derivatives (with apt-get)
		if which apt-get &> /dev/null; then
			apt-get install $PKGSTOINSTALL
		# OpenSuse (with zypper)
		elif which zypper &> /dev/null; then
			zypper in $PKGSTOINSTALL
		# Mandriva (with urpmi)
		elif which urpmi &> /dev/null; then
			urpmi $PKGSTOINSTALL
		# Fedora and CentOS (with yum)
		elif which yum &> /dev/null; then
			yum install $PKGSTOINSTALL
		# ArchLinux (with pacman)
		elif which pacman &> /dev/null; then
			pacman -Sy $PKGSTOINSTALL
		# Else, if no package manager has been founded
		else
			# Set $NOPKGMANAGER
			NOPKGMANAGER=TRUE
			echo "ERROR: impossible to found a package manager in your sistem. Please, install manually ${DEPENDENCIES[*]}."
		fi
		# Check if installation is successful
		if [[ $? -eq 0 && ! -z $NOPKGMANAGER ]] ; then
			echo "All dependencies are satisfied."
		# Else, if installation isn't successful
		else
			echo "ERROR: impossible to install some missing dependencies. Please, install manually ${DEPENDENCIES[*]}."
		fi
	# Else, if user don't want to install missing dependencies
	else
		echo "WARNING: Some dependencies may be missing. So, please, install manually ${DEPENDENCIES[*]}."
	fi
fi

Vado anche qui a commentare:

  1. innanzitutto, un semplice costrutto if verifica che la variabile PKGSTOINSTALL non sia vuota (riga 2) e quindi – in altri termini – verifica che ci sia qualcosa da installare. Ricordo – visto che l’avevo già precisato – che se precedentemente non è stato possibile determinare quali dipendenze siano mancanti e quali presenti, qui risulteranno tutte come mancanti;
  2. a questo punto, viene chiesto all’utente se vuole procedere con l’installazione (la riga 3 stampa la richiesta, la riga 4 salva l’input in READ);
  3. se la risposta è affermativa (riga 6), allora viene ricercato un gestore di pacchetti (righe 8, 11, 14, 17, 20) tra apt-get, zypper, urpmi, yum e pacman, sempre tramite un costrutto if-elif-else;
  4. se è stato trovato un gestore dei pacchetti, allora procede con l’installazione, avvalendosi dell’opportuna istruzione (righe 9, 12, 15, 18, 21). Altrimenti (riga 23), imposta la variabile NOPKGMANAGER a TRUE (riga 25, ci servirà successivamente per controllo) e stampa a schermo un errore (riga 26);
  5. quindi viene verificato l’esito dell’installazione (riga 29). Se l’istruzione di installazione non ha restituito nessun errore (per qualsiasi motivo che abbia portato il gestore di turno ad un fallimento) e se non è presente la variabile NOPKGMANAGER (impostata quando non viene correttamente rilevato un gestore di pacchetti), allora l’installazione ha avuto successo e quindi stampa un messaggio di conferma (riga 30). Se invece le due ipotesi non sono entrambe e contemporaneamente vere (riga 32), allora l’installazione non è andata a buon fine e quindi stampa un messaggio di errore e la lista delle dipendenze segnate come mancanti, che l’utente dovrà (a questo punto) provvedere a installare manualmente (riga  33);
  6. se invece l’utente ha inizialmente risposto negativamente alla richiesta di procedere con l’installazione (riga 36), allora stampa un messaggio di avviso e – anche qui – la lista delle dipendenze da installare manualmente.
Espandi/comprimi Commenti

[…] (in italiano: Bash scripting: verificare e installare dipendenze mancanti) […]

XHTML - Puoi usare questi tag: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>