1. Homepage
  2. Articoli
  3. Video
  4. Bash scripting
  5. Sistema
  6. Tips
  7. News


Datemi una shell!

» Author: Andrea Ganduglia Date: 2005-10-11 02:01:01 Copyright: (c)2005 Andrea Ganduglia

La prima volta che ho capito che nel PC c'era qualcosa che non andava lavoravo per la redazione di Bologna da Vivere che allora aveva un sito statico da aggiornare ogni giorno a manina. I file del sito erano contenuti in una directory e nominati con un prefisso che indicava la data, in questo modo:

10032000_home.html
10032000_pagina1.html
10042000_home.html
10042000_pagina1.html
...
11032000_home.html
11032000_pagina1.html
11042000_home.html
11042000_pagina1.html
...

Dopo un paio di mesi stavamo impazzendo, perché per ritrovare un file che so, scritto il 10 maggio, prima dovevamo scorrere gli 1 di tutti i mesi e tutti gli anni, poi il 2 di tutti i mesi e tutti gli anni, poi 3 e così via. Allora ebbi la fulminante intuizione che avremmo potuto scrivere le date nel formato AAAAMMGG e avere tutti i file ordinati in questo modo:

20000310_home.html
20000310_pagina1.html
...
20000311_home.html
20000311_pagina1.html
...
20000410_home.html
20000410_pagina1.html
...
20000411_home.html
20000411_pagina1.html
...

Bene, intuizione accolta, mezzi per farlo: nessuno. Infatti convertimmo a mano (seleziona il file, tasto destro, rinomina, cambia il nome, etc.) tipo l'ultimo mese e poi desistemmo, trovandoci con un archivio un po' in un modo, un po' nell'altro.

Bene, ma perché ve lo racconto? Perché oggi, dopo aver conosciuto GNU/Linux e le sue shell, scriverei dentro un file chiamato ita2iso.php:

#!/usr/bin/php

foreach(glob("*.html") as $filename){

        $parts=explode("_",$filename);
        $suf=$parts[1];
        $pre=$parts[0];
        $yy=substr($pre,4,4);
        $mm=substr($pre,2,2);
        $dd=substr($pre,0,2);
        $new_filename=$yy.$mm.$dd."_".$suf;

        rename($filename,$new_filename);
}
E poi:
~$ chmod +x ita2iso.php
~$ ./ita2iso.php

Cosa fa questo script? Banalmente, prende tutti i file .html nella directory in cui è lanciato e li rinomina dal formato GGMMAAAA_suffisso.html al formato AAAAMMGG_suffisso.html, che siano uno o centomila. Perché in PHP? Questo non lo so, probabilmente perché mi andava così, magari in un altro momento avrei potuto scriverlo in Bash scripting, Perl, Python, Tcl, Sh ... tanto per dire che non è necessario conoscere PHP per fare una cosa del genere, ma un qualsiasi linguaggio di scripting. Gia, ma...

Che cos'è un linguaggio di scripting?

La cosa migliore, probabilmente, è che consultiate la pagina di Wikipedia che lo descrive. In breve, si tratta di alcune istruzioni da passare al computer perché questo faccia alcune cose. Nel caso illustrato, voi gli dite:

//Linguaggio che voglio usare

//Per ogni file .html in questa directory

	//Dividilo in due parti, al carattere '_'
	//La seconda parte e' suffisso.html
	//La prima parte e' GGMMAAAA
	//Della prima parte, prendi AAAA	
	//Della prima parte, prendi MM
	//Della prima parte, prendi GG
	//Voglio che il nuovo file si chiami AAAAMMGG_suffisso.html
	
	//Rinomina questo file

E perché è utile?

Perché permette di automatizzare le cose, le fa fare al computer al posto vostro e vi risparmia fatica. Un esempio? ok! Mettiamo che la nostra directory contenga 100 file, mentre voi li rinominate a mano attraverso un File manager, io scrivo questo script e lo lancio. Scommettiamo che mentre voi siete intorno al 20esimo file, io ho già finito e sto prendendo un caffé? E voi siete uno che va veloce ;-)

Scripting in ufficio

Dicevo: qualcosa che non va. Quella che ho descritto è una delle situazioni in cui gli utenti si trovano continuamente: dover fare un'operazione ripetitiva e noiosa, nonché soggetta ad errori umani, e doverla fare a mano, nonostante la potenza di calcolo della macchina che si ha di fronte (a proposito: la CPU serve a fare queste cose, per guardare i film ci vuole la RAM). Situazione di cui gli uffici, in particolar modo, sono pieni: rinominare file, spostare grandi quantità di file da un PC ad un altro, creare una ISO di un albero di file system, creare miniature di una serie d'immagini, caricare file via ftp, inviare mail a molti indirizzi diversi...

Cosa? Ho colto la tua attenzione? Inviare mail, dici... Facciamo una piccola digressione e ammettiamo che dobbiate inviare una newsletter settimanale a 1000 indirizzi differenti. In molti posti si usa ancora un client di posta tradizionale, come Outlook Express, Eudora, Thunderbird, etc. che sono ottimi strumenti (più ottimi se presi in ordine inverso), per la posta personale, ma che non sono assolutamente adatti per gestire una lista di indirizzi, tanto più vero se gli indirizzi sono molti, inoltre non potete fare figate-tipo-marketing come aprire la mail con il nome del vostro cliente.

La soluzione? O prendete un elaborato sistema di newsletter, oppure usate uno script diviso in tre parti:

1. lista_indirizzi.txt contiene la lista degli indirizzi a cui volete inviare la newsletter.

Andrea::info@sitoweb.org
Marco::marco.rossi@sitoweb.net
...

2. testo_mail.txt contiene il testo della mail.

Caro {NOMECLIENTE},
questo è l'ultimo articolo di OpenClose.it, leggilo e passa a GNU/Linux.
http://www.openclose.it

3. invia_newsletter.sh è lo script che vi consente di inviare la mail

#!/bin/bash

SUBJECT="$1"
TMPLIST=`tempfile`

cat lista_indirizzi.txt | sort | uniq > $TMPLIST

for i in `cat $TMPLIST`; do
	NOMECLIENTE=`echo $i | awk -F:: '{print $1}'`
	MAILCLIENTE=`echo $i | awk -F:: '{print $2}'`
	TESTOCLIENTE=`cat testo_mail.txt | sed -e s/{NOMECLIENTE}/$NOMECLIENTE/g`
	
	echo $TESTOCLIENTE | mail -s "$SUBJECT" $MAILCLIENTE
	echo -e "`date +%Y-%m-%d` $NOMECLIENTE $MAILCLIENTE $SUBJECT" >> mail_inviate.log
done

rm -f $TMPLIST
savelog mail_inviate.log

Ed ora non vi rimane che fare:

~$ chmod +x invia_newsletter.sh
~$ ./invia_newsletter.sh "Oggetto della mail"

Ora, ovviamente uno script serio avrebbe controlli sulla presenza dell'oggetto, sulla sintassi degli indirizzi mail, etc., ma già così fa delle cose carine, tipo:

  1. controlla che nell'elenco delle mail non ci siano doppioni, se ci sono li elimina;
  2. sostitusce a {NOMECLIENTE} nel testo della mail il nome indicato nell'elenco delle mail;
  3. tiene un log delle mail inviate nel file mail_inviate.log.

Con questo script da questo momento in poi sarete in grado di inviare newsletter ai vostri clienti, soci, amici, semplicemente aggiungendo il loro nome e la loro mail nel primo file e cambiando il testo nel secondo... niente più client che si bloccano, niente più clienti che vi scrivono inferociti perchè hanno ricevuto sette volte la newsletter, niente più tempo perso... è una soluzione semplice ed efficace per risolvere un problema concreto. Ovvero lo scopo della shell UNIX.

Usi il napalm per pulire il pavimento?

Problemi reali, soluzioni semplici. Questo ce lo ricordiamo per dopo, ora un quiz. Cosa usi per pulire il pavimento?

A vederle così non c'è poi questa grande differenza, solo che la prima è acqua con acido citrico (quello che sta nel limone), l'altro è più o meno il Napalm quella cosa che brucia tra 800 e 1.200 gradi Celsius.

E' un esempio paradossale, lo so, ma tornando al software è calzante ogni volta che aprite un editor per le immagini tipo Photoshop, Paint Shop Pro, the GIMP, per ridurne le dimensioni, perché per farlo basta fare:

mogrify -resize 400x300 immagine.jpg

Ma è calzante anche quando usate MS Word per convertire un documento DOC in un RTF, perché basta fare:

abiword --to=rtf file.doc

E perché predere mezza giornata per stampare 100 file in PDF?

for i in *.doc; do
	name=`echo $i | sed -e 's/.doc/.ps/g'
	abiword --print=$name $i && ps2pdf $name && rm -f $name 
done

O avete mai pensato di estrarre del testo da un PDF?

pdftotext documento.pdf > documento.txt

Ok, ok. Qualcosa sui file. Diciamo che vorreste cancellare dalla directory corrente tutti i 500 file tranne uno chiamato progetto_2003.rtf:

rm -f `ls | grep -v progetto_2003.rtf`

Fatto, incredibile no? Ma ammettiamo che stiate scrivendo un progetto e che nella vostra directory condivisa /progetti ci sia una fila di file tipo:

~$ ls /progetti
progetto_marco.rtf
prog-andrea-DEFINITIVO.rtf
no-immagini_paolo.rtf
...

Come fa chi entra in questa cartella a sapere qual è la copia del progetto più aggiornata? guarda le date dei file? Non è che qualcuno ha aperto e salvato il documento sbagliato e quindi le date sono inutili? Beh, la cosa più comoda è battezzare un nome a questo scopo, tipo: ultima_versione.rtf, però che noia, ogni volta che qualcuno mette dentro un nuovo documento deve salvare il proprio file con il nome ultima_versione.rtf, così quando quella non sarà più l'ultima versione deve rinominare il file ultima_versione.rtf in quals'altro (e cosa? tipo ultima_versione_vecchio.rtf? e la volta dopo?) e rinominare il proprio file con il nome corretto. Un po' macchinoso.

Fortunatamente UNIX mette a nostra disposizione i link simbolici, ovvero un modo per assegnare ai file due nomi differnti, ovvero un file due nomi. E allora ecco che:

ln -s progetto_marco.rtf ultima_versione.rtf

E nella nostra directory:

~$ ls /progetti
no-immagini_paolo.rtf
prog-andrea-DEFINITIVO.rtf
progetto_marco.rtf
...
ultima_versione.rtf -> progetto_marco.rtf

Ora, il file ultima_versione.rtf si comporta esattamente come un normale file, se lo aprite leggerete il contenuto di progetto_marco.rtf, le lo spedite via posta elettronica invierete un file chiamato ultima_versione.rtf con il contenuto di progetto_marco.rtf, se lo modificate le modifiche saranno visibili anche dentro il file progetto_marco.rtf, ma se lo cancellate, non cancellerete il file originale, ma solo il suo link. Quindi ecco che il prossimo che metterà nella directory una versione aggiornata del documento lo salverà nella directory condivisa, cancellerà il link simbolico e lo ricostruirà facendolo puntare al proprio file, ovvero:

~$ cp prog-andrea-CORRETTO.rtf /progetti
~$ rm /progetti/ultima_versione.rtf
~$ ln -s /progetti/prog-andrea-CORRETTO.rtf /progetti/ultima_versione.rtf
~$ ls /progetti
no-immagini_paolo.rtf
prog-andrea-CORRETTO.rtf
prog-andrea-DEFINITIVO.rtf
progetto_marco.rtf
...
ultima_versione.rtf -> prog-andrea-CORRETTO.rtf

Da questo momento in poi, chi farà riferimento al file ultima_versione.rtf avrà in cambio il file prog-andrea-CORRETTO.rtf senza che sia stato rinominato alcun file all'interno della directory.

Diciamo che questi sono solo alcuni esempi della potenza della shell UNIX e delle sue possibilità e finiamo questa breve lezione di chimica con un piccolo tocco di stile.

State lavorando da 10 giorni su un documento, sono le 17.45 di venerdì e vorreste andare a casa, solo che prima vorreste anche salvare tutti i dati sulla vostra chiavetta USB, per lavorarci nel weekend. La directory del vostro progetto contiene 74 files, su 4 sub directory differenti e il tutto pesa 230MB; durante l'ultima giornata avete modificato almeno 8 files differenti, ma forse anche qualche altro, e poi ci sono i loghi che il grafico vi ha mandato, ma che non avete ancora guardato. Nella vostra chiavetta, in effetti, c'e' quasi tutto, lo avete giusto salvato ieri sera... basterebbe aggiungere qualche file....

Che fate? Cancellate il contenuto della chiavetta e ricominciate? Andate a caccia dei file che avete modificato, di quelli nuovi, di quelli che avete cancellato? Oppure...

rsync -av --force-delete ~/mioprogetto /mnt/usbkey/mioprogetto

...e lasciate che il software lo faccia per voi? No, perché magari a casa fate delle modifiche e quando tornate in ufficio lunedì potrebbe tornarvi utile:

rsync -av --force-delete /mnt/usbkey/mioprogetto ~/progetto

Ah, per la cronaca rsync è un software che permette di sincronizzare il contenuto di due alberi di directory, lo fa in modo preciso e indolore.

Perché devo imparare tutti questi comandi?

E perché io devo imparare la posizione e il significato di tutte quelle icone quando posso parlare con il PC in linguaggio naturale? In fondo se guardi questa tabellina ti accorgerai che i comandi sono parole:

ls	list		elenca il contenuto delle directory
mv	move		sposta o rinomina un file
cp	copy		copia un file
ln 	link		crea un link 
rm	remove		elemina un file o una directory
...

Ora la shell

Se sei arrivato a questo punto significa che qualcosa ha stuzzicato la tua curiosità, altri continueranno a considerare questa pagina nient'altro che il delirio di un utente GNU/Linux.

Molti considerano la shell un modo primitivo per rapportarsi con un PC, io lo considero il modo più naturale, non solo perché è estremamente veloce, ma perché è chiaro: se digito mv miofile.txt tuofile.txt mi aspetto che il PC rinomini il primo file nel secondo; se invece devo selezionarlo con il mouse, cliccare il tasto destro, scegliere dal menù contestuale "rinomina", posizionarmi di nuovo sul file ed editarne il nome... sì ok, si spiega da solo. Se poi i file sono più d'uno...

Poco più sopra ho detto che la shell è lo strumento giusto per risolvere problemi concreti nel modo più semplice ed economico. Questo significa che attraverso la shell - ed uno o più linguaggi di scripting - sarete in grado di risolvere tutta una serie di situazioni di cui gli esempi citati rappresentano solo un limitatissimo sotto insieme.

In breve, grazie ad una interfaccia a linea di comando, ovvero dove le azioni che il PC deve compiere sono digitate e non descritte con una analogia (ad es. se dovete eliminare un file scriverete rm nomefile.txt e non trascinerete nomefile.txt nel cestino), si elimina tutta la complessità dovuta all'interfaccia grafica e quindi buona parte del lavoro di programmazione, concentrandosi esclusivamente sul bisogno e sul risultato. Aggiungo che gli script possono essere memorizzati in normali file di testo e quindi eseguiti a mano ogni volta che occorre, oppure fatti eseguire al PC in autonomia, magari ciclicamente ad orari ben precisi.

Rispetto ad altri sistemi (e qui il pensiero corre a MS-DOS) la shell di UNIX, e quindi quella di Linux, è molto più potente, tanto che paragonarle non ha quasi significato; è importante però soffermarsi su questo punto per chi non ha avuto altra esperienza che il DOS. Con una UNIX shell potete fare tutto quello che potete fare con una interfaccia grafica, non come nel DOS dove potete fare solo una parte limitata di azioni, tanto che aprire il "Prompt dei comandi" per un utente Windows è sinomino di "risolvere qualche pasticcio". Inoltre, molti delle applicazioni grafiche che girano su sistemi *NIX possiedono una modalità da linea di comando, così che possiate usare proprio quei programmi nei vostri script. Ad esempio Abiword, che è un popolare word processor, simile a MS Word, è tra questi software. Per convertire un file da .doc a .rtf potete aprire il programma e dal menù "File" selezionare "Salva con nome..." oppure utilizzare all'interno dello script un comando già visto:

abiword --to=rtf file.doc

La qualità della conversione (ovvero il risultato) sarà esattamente la stessa, quello che cambia è solo il modo di arrivarci.

Ora, nella mia esperienza, non tutti hanno gli strumenti intellettuali o l'attitudine alla programmazione, infatti non voglio dire approcciarsi ad un PC via shell sia il modo più corretto o quello che tutti dovrebbero utilizzare: le interfacce grafiche esistono proprio per rendere più semplice e più intuitivo l'uso di un PC anche a chi non è in grado di capire o non sia interessato a capire le dinamiche logiche di un sistema operativo. Le interfacce grafiche (dette anche GUI) hanno però un limite evidente, possono fare solo quello che sono state programmate per fare ed una personalizzazione di queste funzioni è molto difficile se non impossibile. Con la linea di comando invece ci si trova di fronte ad un ambiente molto più flessibile, dove è facile, specie in *NIX, costruire software per le proprie esigenze, a partire dai "mattoni" di base che vengono messi a disposizione dell'utente.

Fine: assumi qualcuno che usi la shell per te

Una cosa di cui mi sono reso conto è che negli ambienti di lavoro c'è un continuo bisogno di software per risolvere esigenze specifiche; talmente specifiche che è impossibile trovare sul mercato un software che faccia esattamente quello che si desidera. Mi sono anche reso conto che queste esigenze sono spesso banali, ma che per farvi fronte si utilizzano una serie di strumenti e di persone che - se si calcolasse il TCO - spesso hanno un costo molto superiore al costo delle alternative. Un esempio? Da una storia vera:

Il grafico ha bisogno di arricchire le font del proprio sistema, trova quindi un sito dove sono presenti centinaia di font e decide che a) bisogna scaricarle tutte, o b) ordinare il CD per posta ad un prezzo non troppo economico.
Si opta per la prima ipotesi e si precettano il suo assistente e la segretaria che per cinque giorni faranno questo: collegati al sito, apri la pagina dove sono elencate 10 font, posizionati sulla prima font, clicca con il tasto destro "Salva oggetto con nome...", salva il file .zip in una directory, apri il file .zip, estrai il file .ttf e mettilo nella directory delle font. Posizionati sulla seconda...

Si capisce che questa è una soluzione assurda perché molto costosa in termini di tempo e soprattutto fallibile in termini di risultato, ma è anche l'unica che può venire in mente a chi è abituato a vedere una sola strada, ovvero: se per salvare un singolo file devo fare così, per salvarne mille dovrò fare questa cosa mille volte. Guardate un po' qui invece:

#!/bin/bash

# Directory di destinazione
FONT_DIR="/usr/share/fonts/plusfont"
# Directory temporanera
TEMP_DIR="/tmp/font_temp"
# Modello url del sito. 
URL_MODEL="http://www.font-gratis.net/page.asp?id="

[ ! -d $FONT_DIR ]; mkdir -p $FONT_DIR
[ ! -d $TEMP_DIR ]; mkdir -p $TEMP_DIR

for i in `seq 1 1000`; do
	# controllo che la pagina esista
	if [ wget --spider "$URL_MODEL$i" 2> /dev/null ]; then
		
		# scarico tutti i file in formato .zip
		wget -nd -rl1 -T60 -U Mozilla -A .zip -P $TEMP_DIR "$i"

		for zip in $TEMP_DIR/*.zip; do
			# Decomprimo il file
			unzip $zip -d $TEMP_DIR
		done
		
		# Sposto tutti i file nella dir di destinazione
		mv $TEMP_DIR/*.ttf $FONT_DIR

		# Cancello i file che non servono
		rm -rf $TEMP_DIR/*
	fi
done

[ -d $TEMP_DIR ]; rm -rf $TEMP_DIR
echo "Ho finito `date`" | mail -s "Font scaricate" grafico@azienda.it

Questo script si realizza in circa 10 minuti e fa con assoluta precisione il lavoro di due persone. Questo intendo quando dico "soluzioni semplici a problemi concreti". Grazie a script come questo è possibile nella vita lavorativa di ogni giorno risparmiare tempo ed energie, è possibile che il PC diventi meno stupido ed in grado di aiutarci a risolvere problemi anche banali, o a compiere per noi operazioni ripetitive e noiose.

Per questo ritengo che sia necessario evitare di additare la shell come un territorio esclusivo dei programmatori e poco utile nella vita di tutti i giorni. A mio giudizio è esattamente il contrario e le aziende, anche quelle molto piccole, dovrebbero iniziare a valutare GNU/Linux e le persone che ci lavorano, non solo in termini di grandi infrastrutture, per i sever, i firewall, le macchine di backup, ma come una "nuova" possibilità per risolvere problemi noti, per far fronte ad esigenze molto specifiche. Dovrebbero, insomma, considerare GNU/Linux e le sue shell il coltellino svizzero dell'informatica in azienda.