20080202

Com fer un backup incremental en 65 lineas de PERL



Sempre he volgut tenir una manera fàcil de fer backups incrementals de directoris sencers.

Aquest cap de setmana he dedicat un dia (no sencer) a implementar un procés amb els següents requeriments:

- El procés escanejara el directori d'origen i replicarà exactament tot el contingut al directori de destí
- Si pel camí trobem fitxers o directoris ja existents, comprovarem si s'han modificat
- Si el fitxer no s'ha modificat, no el copiem
- Si el directori ja existeix, no el recreem
- El directori d'origen MAI serà modificat pel procés, nomes es permet passar fitxers o directoris al destí, així minimitzem el perill de fer alguna bestiesa

Per mi, es un backup incremental prou bo.


La idea es instal·lar als nous Desktops de la oficina que van sobrats de disc dur, el procés, configurat perquè faci backup de les dues unitats de xarxa que tenim a la oficina i on tots treballem de forma compartida.

Amés una es una unitat de SAMBA que esta mapejada a un Linux de desenvolupament i l'altre es una unitat remota normal i corrent d'un Fileserver amb Windows.

Dilluns crearé un directori de backup per cada unitat de xarxa a cada PC dels nous i provaré el procés, a veure si tinc èxit.

De moment en local em funciona, us explico una mica com ho he fet:

Com sempre diu el Manel, el PERL es molt potent, val a dir que jo sempre modifico la frase per "El PERL es potent, pero PERILLOS!!!"

De totes maneres, aquest matí m'he instal.lat la versió gratuïta el "Active State Perl" el líder en entorn Windows i he començat a rascar, val a dir que fa un any que tinc experiència demostrada en PHP però el PERL és lleugerament diferent i he hagut de canviar una mica el Xip.

Anem a veure com fer un backup incremental d'un directori origen a un directori destí, en nomes 65 línies de PERL (incloent comentaris i sense fer xapusses per compactar codi)

Declaració de variables i d'intencions:

Les primeres línies son molt avorrides, només fem imports i declarem variables...


use strict;
use warnings;
use File::Find;
use File::Copy;
use File::stat;

my $files_copied=0;
my $files_updated=0;
my $dirs_scanned=0;
my $files_scanned=0;
my $dirs_created=0;

my $source = 'c:/apons/';
my $destination = 'd:/apons2/';
print "Anem a copiar tot el contingut de manera incremental de $source a $destination!\n";


$source serà el directori d'origen (recordem que aquest mai es modificarà) i $destination es el directori de destí, només cal que existeixi, el procés ja crearà tota la estructura de directoris per sota d'ell.

Uff, ja portem 15 lineas, no se pas si aconseguirem fer tot això en les 50 que ens queden :)


find(\&Wanted, $source);

print "Dirs scanned: $dirs_scanned, Files Scanned: $files_scanned\n";
print "Dirs created: $dirs_created, Files copied: $files_copied, Files updated: $files_updated\n";


De fet aquí acaba el "main program", ja que la resta esta a la Subrutina "Wanted", en aquestes 3 línies, simplement cridem a la funció find, que recorre tots els fitxers recursivament partint directori indicat pel segon paràmetre, en aquest cas $source, per cada fitxer s'executarà la subrutina Wanted.

El find de PERL (que podem fer servir gracies al use File::Find; de les primeres línies) em recorda bastant a la comanda de UNIX find . -name "$source" -exec Wanted {} \; (sempre salvant les distancies)

Ah, les altres línies son com un resum de les operacions que hem fet, clar, no ?


sub Wanted
{
my $elemento = $_;
my $source_file=$File::Find::name;
my $destination_file=$destination.substr($source_file, length($source)-1);

if (-d $elemento)
{# Es un directori
$dirs_scanned++;
if (-e $destination_file)
{# Ya existia el directori, no fem res
#print "Directorio $destination_file ya creado NO action \n";
}
else
{# No existia el directori, el creem
$dirs_created++;
print "Vamos a crear -> $destination_file\n";
mkdir $destination_file;
}
}


Anem una mica més al gra, vegem el codi de la subrutina Wanted, aquí es on es realment fem la feina.

my $elemento = $_; asigna el fitxer actual que estem tractant a $elemento;

Assignem a $source_file la variable $File::Find::name que conte el nom complet del fitxer (incloent el directori).

A la línia my "$destination_file=$destination.substr($source_file, length($source)-1);" es on substituïm el directori d'origen pel directori destí.

El "if (-d $elemento)" comprova si el fitxer que estem escanejant es un directori, si aquest es el cas, fem el següent

$dirs_scanned++; incrementem les estadístiques.

if (-e $destination_file), si existeix el directori destí no fem res, en canvi al else, incrementem els $dirs_created, printem un missatge i fem un mkdir per crear el directori destí.

Ja portem 40 línies, ai mareta, no se si arribarem, ens queden 25 !!!



if (-f $elemento)
{# Es un fitxer
$files_scanned++;
if (-e $destination_file)
{# El fitxer ja existeix, mirem la data de modificacio
#print "El fichero $destination_file existe, comprobamos si esta actualizado.\n";
my $source_stat = stat($source_file);
my $source_mtime = $source_stat->mtime();
my $destination_stat = stat($destination_file);
my $destination_mtime = $destination_stat->mtime();
if ($source_mtime > $destination_mtime)
{# Efectiviwonder, el fitxer s'ha modificat, el copiem
print "OJITO, algo ha canviat al fitxer origen $source_file, maxaquem el backup\n";
copy($source_file, $destination_file);
$files_updated++;
}
}
else
{# El fitxer de desti no existeix, el copiem
print "El fichero $destination_file NO existe, lo copiamos.\n";
copy($source_file, $destination_file);
$files_copied++;
}
}
}


El primer if "if (-f $elemento)" controla si el fitxer que estem tractant es realment un fitxer.

El segon, controla si el fitxer de destí ja existeix "if (-e $destination_file)", si es així declarem obtenim les propietats del fitxer origen i el fitxer destí amb les següents línies:


my $source_stat = stat($source_file);
my $source_mtime = $source_stat->mtime();
my $destination_stat = stat($destination_file);
my $destination_mtime = $destination_stat->mtime();


La funció stat, la podem fer servir gracies al "use File::stat;" en aquest cas obtenim el mtime (modification time) del fitxer d'origen i del destí.

Posteriorment comprovem si el "$source_mtime > $destination_mtime" si es així hem de maxacar el fitxer de destí sense pietat, ja que està obsolet:


print "OJITO, algo ha canviat al fitxer origen $source_file, maxaquem el backup\n";
copy($source_file, $destination_file);
$files_updated++;


Com veieu hem aprofitat per printar un bonic missatge i incrementar el contador de fitxers actualitzats.

Finalment anem al else, que es quan el fitxer de destí no existeix, llavors simplement copiem el fitxer al destí, printem un missatge i actualitzem les estadístiques.


print "El fichero $destination_file NO existe, lo copiamos.\n";
copy($source_file, $destination_file);
$files_copied++;



Aquí podeu veure un exemple de una execució incremental:


C:\apons>test.pl
Anem a copiar tot el contingut de manera incremental de c:/apons/ a d:/apons2/!
El fichero d:/apons2//lvg200706120451lb.pdf NO existe, lo copiamos.
OJITO, algo ha canviat al fitxer origen c:/apons/test.pl, maxaquem el backup
El fichero d:/apons2//i_want_you_to_learn_perl.jpg NO existe, lo copiamos.
Vamos a crear -> d:/apons2//proves
El fichero d:/apons2//proves/jquery.js NO existe, lo copiamos.
El fichero d:/apons2//proves/provainplace.html NO existe, lo copiamos.
El fichero d:/apons2//proves/provainplace2.html NO existe, lo copiamos.
El fichero d:/apons2//Superman-RedSon/Superman - Red Son 03/RED SON08.JPG NO existe, lo copiamos.
Dirs scanned: 250, Files Scanned: 2653
Dirs created: 1, Files copied: 6, Files updated: 1


No torno a copiar tot el codi complert, perquè son només 65 línies, una mica de entrenament de copy&paste no us anirà malament ;)

Enjoy!

1 comentario:

Seize dijo...

Amb dos collons!!
use strict;
use warnings;
Això és programar i la resta són tonteries.
Personalment m'he trobat problemes amb el File::Copy, però si no et dona pel sac, perfecte! ;)