A Byte of Python

La soluzione

Ora che il progetto del programma è stabile, si può iniziare a scrivere il codice, che altro non è che una implementazione della soluzione cercata.

Prima versione

Esempio 10.1. Script di backup - Prima versione

				
#!/usr/bin/python
# Filename: backup_ver1.py

import os
import time

# 1. I file  e le directory che sono oggetto del backup vengono specificate in una lista
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Se si sta usando Windows, usare source = [r'C:\Documents', r'D:\Work'] o qualcosa di simile

# 2. Il backup deve essere memorizzato un una directory di backup principale
target_dir = '/mnt/e/backup/' # Ricordare di modificare questo in qualcosa che possa essere usato

# 3. I file vengono salvati in un file in formato zip
# 4. Il nome dell'archivio in formato zip è l'ora e la data corrente
target = target_dir + time.strftime('%Y%m%d%H%M%S') + '.zip'

# 5. Si usa il comando zip (in Unix/Linux) per salvare i file in un archivio in formato zip
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Esegue il backup
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Output

				
$ python backup_ver1.py
Successful backup to /mnt/e/backup/20041208073244.zip
				
				

Entriamo quindi nella fase di test per verificare che il programma funzioni correttamente. Se il programma non si comporta come ci si aspetta, occorre fare un debug per rimuovere eventuali bug (errori) dal programma.

Funzionamento

Si sarà notato che abbiamo convertito il progetto in codice in una modalità step-by-step (NdT: passo-passo).

Si devono usare i moduli os e time, e quindi è necessario importarli. Occorre specificare le directory e i file oggetto del backup nella lista source. La directory dove memorizzare tutti i file di backup è dichiarata nella variabile target_dir. Il nome dell'archivio in formato zip che creeremo è composto dalla data e dall'ora corrente determinata con la funzione time.strftime(). L'archivio avrà anche un'estensione .zip e verrà fisicamente memorizzato nella directory target_dir.

La funzione time.strftime() riceve un argomento di formato come quello usato nel programma precedente. L'argomento di formato %Y verrà poi sostituito dall'anno con il secolo come numero decimale. La specifica %m verrà sostituita dal mese come numero decimale tra 01 e 12, e così via. La lista completa degli argomenti di formato è riportata nel [Python Reference Manual] fornito insieme alla distribuzione di Python. Da notare che queste specifiche sono simili (ma non uguali) a quelle usate nell'istruzione print (usando % seguito dalla tupla).

Il nome del file in formato zip viene creato usando un operatore di addizione che concatena le stringhe, cioè unisce le due stringhe e ne restituisce una nuova. Viene poi creata la stringa zip_command che contiene il comando che si dorà eseguire. Si può controllare se questo comando funziona a dovere eseguendolo direttamente da shell (terminale Linux o Dos prompt).

Il comando zip che usiamo accetta in ingresso opzioni e parametri. L'opzione -q viene usata per indicare che il comando zip dovrebbe funzionare senza fornire output (quietly). L'opzione -r specifica che il comando zip dovrebbe funzionare in modalità ricorsiva (recursively) per le directory, ovvero dovrebbe includere tutte le sottodirectory ed i files presenti all'interno di ciascuna sottodirectory. Le due opzioni possono essere combinate e specificate contemporaneamente con -qr. Le opzioni sono seguite dal nome dell'archivio in formato zip e dalla lista dei file e delle directory oggetto del backup. La lista source viene poi convertita in stringa usando il metodo join di cui è già stato visto l'uso.

Al termine viene eseguito il comando vero e proprio usando la funzione os.system che esegue il comando come se fosse eseguito dal sistema, ad esempio in una shell. Viene restituito 0 se il comando è stato eseguito correttamente o un numero diverso in caso di errore.

A seconda dell'esito del comando, viene stampato un messaggio appropriato che indica se il backup ha avuto successo o meno.

Nota per utenti Windows

E' possibile impostare la lista source e la directory target con il nome di qualsiasi file e directory, ma in Windows occorre fare un po' di attenzione. Windows usa il simbolo di backslash (\) come carattere separatore di directory, mentre Python lo usa per rappresentare sequenze di escape!

Per questo è; necessario rappresentare il carattere di backslash usando una sequenza escape o usando stringhe raw. Per esempio: 'C:\\Documents' o r'C:\Documents' ma non 'C:\Documents'; in quest'ultimo caso, infatti, si userebbe una sequenza di escape non documentata: \D !

Ora che lo script per il backup funziona, lo si può usare ogni volta che è necessario fare il backup di file importanti. Gli utenti Linux/Unix dovranno usare il metodo eseguibile discusso precedentemente in modo da poter eseguire lo script di backup dovunque. Questa fase viene chiamata operativa, o di deployment del software.

Il programma preso in esame funziona perfettamente ma, solitamente, i primi programmi non funzionano proprio come ci si aspetta. Per esempio, ci potrebbero essere problemi se non si è progettato il programma in modo corretto o se abbiamo fatto errori di digitazione nel codice, ecc... Ecco perché spesso è necessario tornare alla fase di progettazione o eseguire un debug del programma.

Seconda versione

Sebbene la prima versione dello script funziona a dovere, vogliamo apportare alcune rifiniture in modo che possa funzionare meglio su base giornaliera. Questa fase è chiamata manutenzione del software.

Una delle rifiniture da apportare è un miglior meccanismo per la nomenclatura del file che usi l'ora come base per il nome del file, lo memorizzi in una directory avente per nome la data corrente e che sia interna alla directory principale di backup. Il vantaggio è che il backup verrà memorizzato in modo gerarchico e perciò sarà più facile da gestire. Inoltre il nome dei file risulterà più corto, le directory separate facilitano il controllo nel caso di backup programmati su base giornaliera, ecc...

Esempio 10.2. Script di backup - Seconda versione

				
#!/usr/bin/python
# Filename: backup_ver2.py

import os
import time

# 1. I file  e le directory che sono oggetto del backup vengono specificate in una lista
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Se si sta usando Windows, usare source = [r'C:\Documents', r'D:\Work'] o qualcosa di simile

# 2. Il backup deve essere memorizzato un una directory di backup principale
target_dir = '/mnt/e/backup/' #  Ricordare di modificare questo in qualcosa che possa essere usato

# 3. I file vengono salvati in un file in formato zip
# 4. Il giorno corrente è il nome della sottodirectory nella directory principale
today = target_dir + time.strftime('%Y%m%d')
# L'ora corrente è il nome dell'archivio in formato zip
now = time.strftime('%H%M%S')

# Crea la sottodirectory se non esiste
if not os.path.exists(today):
	os.mkdir(today) # crea la directory
	print 'Successfully created directory', today

# Il nome dell'archivio in formato zip
target = today + os.sep + now + '.zip'

# 5. Si usa il comando zip (in Unix/Linux) per salvare i file in un archivio in formato zip
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Esegue il backup
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Output

				
$ python backup_ver2.py
Successfully created directory /mnt/e/backup/20041208
Successful backup to /mnt/e/backup/20041208/080020.zip

$ python backup_ver2.py
Successful backup to /mnt/e/backup/20041208/080428.zip

				
				

Funzionamento

Buona parte del programma è rimasta la stessa. Le modifiche riguardano il controllo sull'esistenza di una directory avente per nome la data corrente e posta all'interno della directory principale di backup; tale controllo viene fatto con la funzione os.exists. Se la directory non esiste, essa viene creata usando la funzione os.mkdir.

Da notare l'uso della variabile os.sep, la quale rappresenta il carattere di separazione delle directory in accordo col sistema operativo in uso, che sarà '/' in Linux/Unix, '\\' in Windows e ':' in Mac OS. L'uso di os.sep invece dei caratteri letterali rende il programma portabile e funzionante su tutti i sistemi.

Terza versione

La seconda versione funziona perfettamente quando si fanno molti backup, ma resta il problema della determinazione del loro contenuto. Per esempio, si supponga di aver apportato delle modifiche ad un programma o ad una presentazione e di voler associare la tipologia delle stesse al nome dell'archivio salvato in formato zip. Questo risultato può essere ottenuto facilmente allegando un breve commento fornito dall'utente al nome dell'archivio zip.

Esempio 10.3. Script di backup - Terza versione (non funzionante!)

				
#!/usr/bin/python
# Filename: backup_ver2.py

import os
import time

# 1. I file  e le directory che sono oggetto del backup vengono specificate in una lista
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Se si sta usando Windows, usare source = [r'C:\Documents', r'D:\Work'] o qualcosa di simile a questo

# 2. Il backup deve essere memorizzato un una directory di backup principale
target_dir = '/mnt/e/backup/' # Ricordare di modificare questo in qualcosa che possa essere usato

# 3. I file vengono salvati in un file in formato zip
# 4. Il giorno corrente è il nome della sottodirectory nella directory principale
today = target_dir + time.strftime('%Y%m%d')
# L'ora corrente è il nome dell'archivio in formato zip
now = time.strftime('%H%M%S')

# Accetta un commento dell'utente per creare il nome dell'archivio in formato zip
comment = raw_input('Enter a comment --> ')
if len(comment) == 0: # controlla se è stato immesso un commento
	target = today + os.sep + now + '.zip'
else:
	target = today + os.sep + now + '_' +
		comment.replace(' ', '_') + '.zip'

# Crea la sottodirectory se non esiste
if not os.path.exists(today):
	os.mkdir(today) # crea la directory
	print 'Successfully created directory', today

# 5. Si usa il comando zip (in Unix/Linux) per salvare i file in un archivio in formato zip
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Esegue il backup
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Output

				
$ python backup_ver3.py
File "backup_ver3.py", line 25
target = today + os.sep + now + '_' +
					^
SyntaxError: invalid syntax
					
				

(Mal)funzionamento

Questo programma non funziona!. Python dice che c'è un errore, cioè lo script non soddisfa la struttura che Python si aspetta di trovare. Osservando l'errore emesso da Python, si nota che viene riportato il punto in cui è stato scoperto! Quindi è necessario fare un debug del programma a partire da questa riga.

Da un'attenta osservazione si nota che la linea logica è stata divisa in due linee fisiche senza specificare che queste linee fisiche fanno parte della stessa linea logica. In questa linea logica Python trova infatti l'operatore di addizione (+) senza nessun operando, e perciò non sa come continuare. Per poter usare una linea logica scritta su più linee fisiche si deve usare il carattere di backslash alla fine di ogni linea fisica incompleta. Con questo in mente possiamo correggere il programma. Questa fase è chiamata correzione degli errori (bug fixing).

Quarta versione

Esempio 10.4. Script di backup - Quarta versione

				
#!/usr/bin/python
# Filename: backup_ver2.py

import os, time

# 1. I file  e le directory che sono oggetto del backup vengono specificate in una lista
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Se si sta usando Windows, usare source = [r'C:\Documents', r'D:\Work'] o qualcosa di simile a questo

# 2. Il backup deve essere memorizzato un una directory di backup principale
target_dir = '/mnt/e/backup/' # Ricordare di modificare questo in qualcosa che possa essere usato

# 3. I file vengono salvati in un file in formato zip
# 4. Il giorno corrente è il nome della sottodirectory nella directory principale
today = target_dir + time.strftime('%Y%m%d')
# L'ora corrente è il nome dell'archivio in formato zip
now = time.strftime('%H%M%S')

# Accetta un commento dell'utente per creare il nome dell'archivio in formato zip
comment = raw_input('Enter a comment --> ')
if len(comment) == 0: # controlla se è stato immesso un commento
	target = today + os.sep + now + '.zip'
else:
	target = today + os.sep + now + '_' + \
		comment.replace(' ', '_') + '.zip'
 	# Notice the backslash!

# Crea la sottodirectory se non esiste
if not os.path.exists(today):
	os.mkdir(today) # crea la directory
	print 'Successfully created directory', today

# 5. Si usa il comando zip (in Unix/Linux) per salvare i file in un archivio in formato zip
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Esegue il backup
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Output

				
$ python backup_ver4.py
Enter a comment --> added new examples
Successful backup to /mnt/e/backup/20041208/082156_added_new_examples.zip

$ python backup_ver4.py
Enter a comment -->
Successful backup to /mnt/e/backup/20041208/082316.zip
				
				

Funzionamento

Il programma ora funziona e permette di valutare le migliorie fatte alla versione tre. I commenti dell'utente si estraggono usando la funzione raw_input e si controlla se l'utente ha effettivamente annotato qualcosa valutando la lunghezza dell'input con la funzione len. Se l'utente ha premuto solo il tasto enter per qualche ragione (era solo un backup routinario o non sono state fatte modifiche speciali), il programma procede come nel caso della versione precedente.

Se è stato fornito un commento, questo viene attaccato al nome dell'archivio zip prima dell'estensione .zip. Si noti che sono stati sostituiti tutti gli spazi presenti nel commento con caratteri di sottolineatura per semplificarne la gestione.

Ulteriori rifiniture

La quarta versione è uno script funzionante e soddisfacente per la maggior parte degli utenti, ma c'è sempre posto per le migliorie. Ad esempio, si può includere un livello di verbosità al programma potendo specificare un'opzione -v .

Un'altra miglioria possibile potrebbe essere la possibilità di passare file e directory extra allo script da linea di comando. Si potranno ottenere dalla lista sys.argv e quindi aggiungerli alla lista source con il metodo extend fornito dalla classe list .

Una miglioria molto comoda è l'uso del comando tar al posto del comando zip, che in combinazione con gzip, permette una creazione più rapida e compatta del file di backup. In Windows questo archivio è gestito dall'applicazione WinZip che riconosce i files .tar.gz . Il comando tar è disponibile di default sulla quasi totalità dei sistemi Linux/Unix. Gli utenti Windows possono scaricarlo ed installarlo.

La stringa di comando diverrà come segue:

			
tar = 'tar -cvzf %s %s -X /home/swaroop/excludes.txt' % (target, ' '.join(srcdir))
			
			

Le opzioni vengono spiegate di seguito.

  • -c indica la creazione di un archivio.

  • -v indica la verbosità, il comando dovrebbe essere più 'prolisso'.

  • -z indica che deve essere usato il filtro gzip.

  • -f indica la forzatura nella creazione dell'archivio, eventualmente sostituendo un file esistente con lo stesso nome.

  • -X indica un file che contiene una lista di nomi di file che devono essere esclusi (excluded) dal backup. Per esempio, specificando *~ nel backup non verrà incluso nessun file che termina con ~.

Importante

Il modo migliore per creare questo tipi di archivi richiede l'uso dei moduli zipfile o tarfile. Essi fanno parte della libreria standard di Python e sono disponibili all'uso. Usando queste librerie si evita la necessità di ricorrere a os.system, il cui uso facilmente porta ad introdurre degli errori.

Nonostante ciò nell'esempio si ricorre a os.system a scopo puramente illustrativo e in modo tale che l'esempio risultasse comprensibile da tutti ed abbastanza reale da essere utile.