A Byte of Python

La solució

Un cop disposem d'un disseny del nostre programa, podem escriure el codi que serà la implementació de la nostra solució.

Primera versió

Example 10.1. Script de còpies de seguretat - La primera versió

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

import os
import time

# 1. Els fitxers i els directoris que han de ser guardats, s'especificaran a una llista.
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Des de Windows fer servir source = [r'C:\Documents', r'D:\Work'] o similar


# 2. La còpia de seguretat ha de ser guardada en un directori principal de còpies de seguretat.
target_dir = '/mnt/e/backup/' # Canviar aquest pel lloc que fem servir

# 3. Els fitxers es guarden en un fitxer zip. 
# 4. El nom de l'arxiu zip és la data i hora actual.
target = target_dir + time.strftime('%Y%m%d%H%M%S') + '.zip'

# 5. Fem servir la comanda zip (Unix/Linux) per a col·locar els fitxers a un arxiu zip
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Executem la còpia
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Sortida

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

Ara passem a la fase de prova on comprovem el correcte funcionament del nostre programa. Si no es comporta com s'esperava, haurem de depurar-lo, és a dir, eliminar els errors o bugs del programa.

Com funciona

Podem veure com hem convertit pas a pas, el nostre disseny en codi.

Primer importem els mòduls os i time. A continuació especifiquem a la llista source els fitxers i directoris a copiar. El directori objectiu és on guardarem totes les còpies de seguretat i s'especifica a la variable target_dir. El nom del fitxer zip que crearem és la data i hora actual, que obtenim a partir de la funció time.strftime(). També especifiquem l'extensió .zip i què serà col·locat al directori target_dir.

La funció time.strftime() pren com a paràmetre el format de la data i hora. L'especificació %Y serà substituida per l'any (en dos dígits). %m prendrà per valor el mes en forma de número entre el 01 i el 12. I així amb la resta. La llista completa d'especificacions pot trobar-se al manual de referència de Python ([Python Reference Manual]) dins la distribució de Python. Podem observar que és similar, malgrat no igual, a l'especificació de la instrucció print. Ambdós fan servir % seguit per una tupla.

Creem el nom de fitxer zip fent servir l'operador de concatenació de strings. A continuació creem el string zip_command què conté la comanda que executarem. Es pot comprovar que la comanda funciona, executant-la a l'intèrpret d'ordres (terminal Linux o DOS).

La comanda zip que fem servir té algunes opcions i paràmetres. L'opció -q indica que la comanda ha de funcionar en mode quiet (silenciós). L'opció -r indica que ha de funcionar de manera recursiva (és a dir, ha d'incloure també els fitxers dels subdirectoris). Les dues opcions han estat combinades de manera breu amb -qr. A continuació s'afegeix el nom de l'arxiu a crear seguit per la llista de fitxers a copiar. Convertim la llista source en un string amb el mètode join.

Finalment executem la comanda fent servir la funció os.system que l'executa com si l'haguèssim introduit a l'interpret d'ordres. Aquesta funció retorna 0 quan l'execució ha tingut èxit, o un un número d'error altrament.

Depenent de la sortida de la comanda, informem l'usuari de si la còpia de seguretat ha tingut o no èxit. I això és tot: acabem de crear un script que fa còpies de seguretat dels nostres fitxers importants!

Nota pels usuaris de Windows

Podem assignar a la llista source i a la variable target a qualsevol fitxer i directori, però hem d'anar amb una mica de compte en Windows. El problema és què Windows fa servir la contrabarra (\) com a caràcter separador de directoris però Python fa servir aquest caràcter per a representar seqüències d'escapament.

Així, ens caldrà representar la contrabarra fent servir una seqüència d'escapament o bé fer servir strings crus (no processats). Per exemple, 'C:\\Documents' o r'C:\Documents' però mai farem servir 'C:\Documents' - o estaríem especificant la seqüència d'escapament desconeguda \D !

Ara que tenim el nostre programa en funcionament, podem fer-lo servir sempre que vulguem per a fer còpies de seguretat. Es recomana als usuaris de Linux/unix que facin servir el mètode d'execució discutit anteriorment, de manera que es pugui executar el script en qualsevol moment i des de qualsevol lloc. Aquesta fase es coneix com a fase d'operació.

El programa anterior funciona correctament, però normalment els primers programes no funcionen com s'esperava. Per exemple, poden aparèixer problemes si no l'hem dissenyat adequadament, o si hem fet algun error a l'hora de teclejar el codi. En aquests casos, ens caldrà tornar enrere a la fase de disseny o a la d'implementació, i depurar el programa.

Segona versió

La primera versió del nostre script funciona. Amb tot, podem fer-li alguns canvis de manera que funcioni millor per l'ús diari. D'això se'n diu la fase de manteniment del programari.

Un dels canvis que podrien ser útils seria un millor mecanisme d'anomenar els fitxers - fent servir l'hora (time) com a nom del fitxer dins un directori amb la data actual (date) com a directori dins del directori principal de les còpies de seguretat. Un dels avantatges és que les nostres còpies serien emmagatzemades en forma jeràrquica, el que faria més fàcil la seva gestió. Un altre avantatge estaria en que la longitud dels noms dels fitxers seria molt menor. Encara un altre avantatge és que els directoris separats ofereixen una manera més ràpida de comprovar que s'ha realitzat una còpia cada dia donat que un directori només es crearà quan s'hagi fet una còpia de seguretat d'aquell dia.

Example 10.2. Script de còpies de seguretat - La segona versió

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

import os
import time

# 1. Els fitxers i els directoris que han de ser guardats, s'especificaran a una llista.
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Des de Windows fer servir source = [r'C:\Documents', r'D:\Work'] o similar

# 2. La còpia de seguretat ha de ser guardada en un directori principal de còpies de seguretat.
target_dir = '/mnt/e/backup/' # Canviar aquest pel lloc que fem servir

# 3. Els fitxers es guarden en un fitxer zip.
# 4. El dia actual és el nom del subdirectori al directori principal
today = target_dir + time.strftime('%Y%m%d')
# L'hora actual és el nom de l'arxiu zip 
now = time.strftime('%H%M%S')

# Crea el subdirectori si encara no existeix 
if not os.path.exists(today):
	os.mkdir(today) # make directory
	print 'Successfully created directory', today

# El nom de l'arxiu zip
target = today + os.sep + now + '.zip'

# 5. Fem servir la comanda zip (Unix/Linux) per a col·locar els fitxers a un arxiu zip 
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Executem la còpia
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Sortida

				
$ 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

				
				

Com funciona

La major part del programa no ha canviat. El canvi principal és que ara comprovem si hi ha un directori amb la data d'avui com a nom dins del directori principal de les còpies de seguretat. Per a això, fem servir la funció os.exists. Si no existeix, el creem amb la funció os.mkdir.

Fixem-nos com fem servir la variable os.sep. Aquesta ens ofereix el separador de directoris corresponent al sistema operatiu que fem servir. És a dir, tindrà com a valor '/' a Linux/Unix, '\\' a Windows i ':' a Mac OS. L'ús de os.sep en comptes dels caràcters directament, fa que el nostre programa sigui més portable i funcione sobre aquests sistemes.

Tercera versió

La segona versió va bé per fer algunes còpies de seguretat. Però, quan n'hi ha moltes, trobo difícil diferenciar els objectius de les diferents còpies de seguretat. Per exemple, podria haver fet alguns canvis importants a un programa o presentació, i voler associar aquests canvis al nom de l'arxiu zip. Això es pot aconseguir fàcilment adjuntant un comentari de l'usuari al nom de l'arxiu zip.

Example 10.3. Script de còpies de seguretat - La tercera versió (no funciona!)

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

import os
import time

# 1. Els fitxers i els directoris que han de ser guardats, s'especificaran a una llista. 
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Des de Windows fer servir source = [r'C:\Documents', r'D:\Work'] o similar

# 2. La còpia de seguretat ha de ser guardada en un directori principal de còpies de seguretat.
target_dir = '/mnt/e/backup/' # Remember to change this to what you will be using

# 3. Els fitxers es guarden en un fitxer zip. 
# 4. El dia actual és el nom del subdirectori al directori principal
today = target_dir + time.strftime('%Y%m%d')
# L'hora actual és el nom de l'arxiu zip 
now = time.strftime('%H%M%S')

# Obté el comentari de l'usuari per a crear el nom del fitxer
comment = raw_input('Enter a comment --> ')
if len(comment) == 0: # comprova que hagi estat introduït un comentari
	target = today + os.sep + now + '.zip'
else:
	target = today + os.sep + now + '_' +
		comment.replace(' ', '_') + '.zip'

# Crea el subdirectori si encara no existeix
if not os.path.exists(today):
	os.mkdir(today) # make directory
	print 'Successfully created directory', today

# 5. Fem servir la comanda zip (Unix/Linux) per a col·locar els fitxers a un arxiu zip
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Executem la còpia
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Sortida

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

Com (no) funciona

Aquest programa no funciona!. Python ens diu que hi ha un error sintàctic que vol dir que el programa no satisfà l'estructura que Python espera trobar. Si observem el missatge d'error que ens dóna, també ens informa on s'ha detectat l'error. Així comencem a depurar el programa des d'aquesta línia.

Fixant-nos amb atenció, trobem que la línia lògica s'ha dividit en dues línies físiques, però no havíem dit enlloc que aquestes dues línies haguessin d'estar juntes. Bàsicament, Python ha trobat l'operador d'addició (+) sense cap operand a aquesta línia lògica, i per tant, no sap com continuar. Recordem que podem indicar que una línia lògica continua a la següent línia física amb la contrabarra al final de la primera línia física. Corregim l'error del nostre programa.

Quarta versió

Example 10.4. Script de còpies de seguretat - La quarta versió

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

import os, time

# 1. Els fitxers i els directoris que han de ser guardats, s'especificaran a una llista.
source = ['/home/swaroop/byte', '/home/swaroop/bin']
# Des de Windows fer servir source = [r'C:\Documents', r'D:\Work'] o similar

# 2. La còpia de seguretat ha de ser guardada en un directori principal de còpies de seguretat.
target_dir = '/mnt/e/backup/' # Remember to change this to what you will be using

# 3. Els fitxers es guarden en un fitxer zip. 
# 4. El dia actual és el nom del subdirectori al directori principal
today = target_dir + time.strftime('%Y%m%d')
# L'hora actual és el nom de l'arxiu zip
now = time.strftime('%H%M%S')

# Obté el comentari de l'usuari per a crear el nom del fitxer
comment = raw_input('Enter a comment --> ')
if len(comment) == 0: # comprova que hagi estat introduït un comentari
	target = today + os.sep + now + '.zip'
else:
	target = today + os.sep + now + '_' + \
		comment.replace(' ', '_') + '.zip'
 	# Notice the backslash!

# Crea el subdirectori si encara no existeix 
if not os.path.exists(today):
	os.mkdir(today) # make directory
	print 'Successfully created directory', today

# 5. Fem servir la comanda zip (Unix/Linux) per a col·locar els fitxers a un arxiu zip
zip_command = "zip -qr '%s' %s" % (target, ' '.join(source))

# Executem la còpia
if os.system(zip_command) == 0:
	print 'Successful backup to', target
else:
	print 'Backup FAILED'
				
				

Sortida

				
$ 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
				
				

Com funciona

Aquest programa funciona. Analitzem les millores que l'hem incorporat respecte a la versió 3. Obtenim el comentari de l'usuari amb la funció raw_input i a continuació comprovem si l'usuari realment ha introduït alguna cosa, comprovant la longitud de l'entrada amb la funció len. Cas que l'usuari simplement hagi pres la tecla enter (potser simplement es tracta d'una còpia de seguretat rutinària que no correspon a cap canvi especial), llavors procedim com ho fèiem abans.

En canvi, si s'ha introduït un comentari, afegim aquest al nom de l'arxiu zip tot just abans de l'extensió .zip. Notem que canviem els espais per guions baixos per a simplificar la manipulació d'aquests noms.

Més millores

La quarta versió és un script que funciona satisfactòriament per a la majoria d'usuaris. Però sempre hi ha lloc per a fer millores. Per exemple, podem incloure l'opció -v per fer que el programa generi més informació durant el seu funcionament.

Una altra millora pot ser permetre recollir fitxers i directoris des de la línia de comandes. Aquests es poden obtenir de la llista sys.argv, i després passar-los a la nostra llista source mitjançant el mètode extend que ens proporciona la classe list.

Una millora que m'agrada és l'ús de la comanda tar en comptes de zip. Quan fem servir tar juntament amb gzip, el procés sol ser més ràpid i l'arxiu resultant molt menor. L'aplicació WinZip és capaç de manegar els fitxers .tar.gz des de Windows. La comanda tar es troba disponible per defecte a la majoria de les instal·lacions Linux/Unix. Els usuaris de Windows poden descarregar-la des de http://gnuwin32.sourceforge.net/packages/tar.htm.

La línia de comanda serà ara:

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

Les opcions s'expliquen a continuació.

  • -c indica creació d'un arxiu.

  • -v indica verbose, és a dir, fer que el programa generi més informació.

  • -z indica que es faci servir el filtre gzip.

  • -f indica forçar la creació de l'arxiu. És a dir, en cas que existeixi un fitxer amb el mateix nom, serà substituït pel nou.

  • -X indica un fitxer que conté una llista de fitxers que han de ser exclosos de la còpia. Per exemple, podem especificar *~ en aquest fitxer de manera que no s'inclogui a la còpia cap fitxer que finalitzi amb ~.

Important

La manera preferida de crear aquesta classe d'arxius seria fent servir els mòduls zipfile o tarfile. Aquests mòduls formen part de la biblioteca estàndard de Python i estan disponibles per ser utilitzats. L'ús d'aquestes biblioteques ens permet evitar les crides a os.system, cosa generalment recomanable per l'alt risc a cometre errors costosos.

Hem fet servir os.system per a crear les còpies de seguretat només amb un propòsit pedagògic, fent que l'exemple fos prou senzill d'entendre a l'hora que prou real per a ser útil.