Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??
I keep forgetting making backups of my dotfiles (and other files and directories too), so I wrote the following script that somewhat automates the process. It can be run by cron.
#!/usr/bin/perl # # Script to backup dot- and other files # # Copyright (C) 2009, Sven-Thorsten Fahrbach # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses>. use strict; use warnings; use File::Path; use File::Basename; use File::Spec; use File::Copy; use Cwd; use Getopt::Long; use Fcntl qw/ :DEFAULT :flock /; my $VERSION = "0.9"; ############################################################## # VARIABLE DECLARATIONS AND THE LIKE ############################################################## # get a timestamp in the format YYYYMMDDHMS my ($year, $month, $dayofmonth, $hour, $minute, $second) = (localtime())[5, 4, 3, 2, 1, 0]; $year += 1900; $month++; my $timestamp = "$year$month$dayofmonth-$hour$minute$second"; # standard values for config file/command line options my $dotbackup_home = get_dotbackup_home(); my $working_dir = $ENV{HOME}; my $filename = "dotbackup$timestamp"; my $destination_dir = getcwd; my $path_to_conf_file = get_path_to_conf_file(); my $path_to_log_file = File::Spec->catfile($dotbackup_home, "log"); my $path_to_file_list = File::Spec->catfile($dotbackup_home, "files" +); my $compression_level = 1; my $gzip_options = ''; my $tar_options = ''; my $show_help = ''; my $show_version = ''; # this is the array with the files that are to be backed up my @backup_files = get_files(); # $log_level can be: # 0 - quiet mode, no logging # 1 - errors # 2 - warnings # 3 - info # 4 - debug my $log_level = 4; # Get values from the configuration file. Default values will be overw +ritten # with values from the config. parse_config($path_to_conf_file) if $path_to_conf_file; # Get command line options. These will overwrite default options and v +alues # we got from the configuration file. my $result = GetOptions("working-dir=s" => \$working_dir, "filename=s" => \$filename, "destination-dir=s" => \$destination_dir, "logfile=s" => \$path_to_log_file, "file-list=s" => \$path_to_file_list +, "log-level=i" => \$log_level, "compression-level=i" => \$compression_level +, "gzip-options=s" => \$gzip_options, "tar-options=s" => \$tar_options, "help" => \$show_help, "version" => \$show_version); help_and_exit() if $show_help; version_and_exit() if $show_version; $compression_level = 1 if $compression_level < 1; $compression_level = 9 if $compression_level > 9; # commands that have to be invoked by us my $tar = "tar -cf "; my $gzip = "gzip -$compression_level "; #################################################################### # MAIN #################################################################### # declare LOG file handle my $LOG; sysopen ($LOG, $path_to_log_file, O_WRONLY | O_APPEND | O_CREAT) or die "Can't open $path_to_log_file: $!"; # We'll try and keep a lock on the log file for the duration of our sc +ript flock($LOG, LOCK_EX) or die "Can't get a lock on $path_to_log_f +ile"; logger("invoked script", 3); logger("compression level is $compression_level", 3); # try to append leftover command line arguments to our @backup_files foreach (@ARGV) { my $cur_file; unless (File::Spec->file_name_is_absolute($_)) { $cur_file = File::Spec->catfile($working_dir, $_); } next unless -e $cur_file; push @backup_files, File::Spec->canonpath($cur_file); } if (@backup_files == 0) { print "Nothing to do\n"; logger("file stack empty - nothing to do", 3); exit(0); } # build tar-string $tar .= "$tar_options " if $tar_options; my $absolute_filename = File::Spec->catfile($destination_dir, $filenam +e . ".tar"); $tar .= "$absolute_filename "; foreach (@backup_files) { # Remove trailing slash or else tar will archive to contents of th +e # directory instead of the directory itself which is probably not +what # we want. s/\/$//; $tar .= "$_ "; } logger("Archiving " . scalar(@backup_files) . " entries", 3); logger("tar string: $tar", 4); # invoke tar my $tar_result; $tar_result = `$tar`; if ($!) { logger("tar error: $!", 1); logger("exited abnormally", 1); die "tar exited abnormally: $!"; } logger("tar exited successfully", 4); logger("tar output: $tar_result", 4); # invoke gzip $gzip .= "$gzip_options " if $gzip_options; $gzip .= $absolute_filename; my $gzip_result; $gzip_result = `$gzip`; if ($!) { logger("gzip error: $!", 1); logger("gzip exited abnormally", 1); die "gzip exited abnormally: $!"; } logger("gzip exited successfully", 4); logger("gzip output: $gzip_result", 4); logger("script exiting successfully", 3); # release lock on log file, close FH and exit successfully flock($LOG, LOCK_UN); close($LOG); exit(0); ############################################################### # SUBROUTINES ############################################################### sub parse_config { my $conf_filename = shift; unless (open(CONF, $conf_filename)) { warn "Could not open config $conf_filename"; return; } while (<CONF>) { chomp; next if /^\s*$/; # ignore blank lines next if /^\s*#/; # ignore lines consisting of comments +only if (/^working-dir\w*=\w*(.+)$/) { unless (-d $1) { warn "in $conf_filename line $.: Directory does not ex +ist"; next; } $working_dir = $1; next; } if (/^destination-dir\s*=\s*(.+)$/) { unless (-d $1) { warn "in $conf_filename line $.: Directory does not ex +ist"; next; } $destination_dir = $1; next; } if (/^log-file\s*=\s*(.+)$/) { if (-d $1) { warn "in $conf_filename line $.: $1 is a directory"; next; } unless (-e $1) { unless (open(LOG, ">", $1)) { warn "in $conf_filename line $.: Cannot open $1 fo +r writing: $!"; next; } print LOG "[" . scalar(localtime()) . "] Created logfi +le\n"; } close(LOG); $path_to_log_file = $1; next; } if (/^file-list\s*=\s*(.+)$/) { if (-d $1) { warn "in $conf_filename line $.: $1 is a directory"; next; } unless (-e $1) { warn "in $conf_filename line $.: $1 does not exist"; next; } $path_to_file_list = $1; next; } if (/^tar-options\s*=\s*(.*)$/) { $tar_options = $1; } if (/^gzip-options\s*=\s*(.*)$/) { $gzip_options = $1; } if (/^compression-level\s*=\s*(\d)$/) { $compression_level = $1; } if (/^filename\s*=\s*(.+)$/) { $filename = "$1$timestamp"; next; } } } sub get_files { unless (open(FILES, $path_to_file_list)) { warn "Cannot open $path_to_file_list: $!"; return } my @filelist; while (<FILES>) { chomp; next if /^\s*$/; # ignore blank lines next if /^\s*#/; # ignore lines containing only commen +ts my $cur_file; unless (File::Spec->file_name_is_absolute($_)) { $cur_file = File::Spec->catfile($working_dir, $_); } next unless -e $cur_file; push @filelist, File::Spec->canonpath($cur_file); } close(FILES); return @filelist; } sub logger { my ($message, $level) = @_; return if $level > $log_level; # name log levels my @level_names = qw( NONE ERROR WARNING INFO DEBUG ); # autoflush output for current scope only local $| = 1; # DEBUG #print "\@level_names:\n"; #print "\t$_\n" foreach @level_names; #print "\$level: $level\n"; #print "\$message: $message\n"; print $LOG "[" . scalar(localtime()) . "] " . $level_names[$level] + . ": $message\n"; } sub get_dotbackup_home { my $dotbackup_home_path = File::Spec->catdir($ENV{HOME}, ".dotback +up"); unless (-d $dotbackup_home_path) { mkpath($dotbackup_home_path) or die "Could not create dir $dot +backup_home_path: $!"; } return $dotbackup_home_path; } sub get_path_to_conf_file { my $conf_file_path = File::Spec->catfile($dotbackup_home, "conf"); #GetOptions("config=s" => \$path); #return if $path == undef; unless (-e $conf_file_path) { # warn "File $conf_file_path does not exist.\n"; return; } if (-d $conf_file_path) { warn "$conf_file_path is a directory.\n"; return; } return File::Spec->canonpath($conf_file_path); } sub help_and_exit { print <<EOHELP; Usage: $0 [OPTION]... [FILE]... Make a backup from a list of files. Save backup tarball to a specified + path (the current directory by default). -c, --compression-level 1-9, 1 is fastest, 9 is best -d, --destination-dir path to the output directory, default is \ +$HOME --filename filename of the generated tarball (without + the suffix) --file-list path to the list of files to be backed up -g, --gzip-options additionals options to pass to gzip -h, --help give this help --log-level 0-4, 0 is quiet, 4 is the most verbose --logfile path to the log file -t, --tar-options additional options to pass to tar -v, --version display version and exit -w, --working-dir path to the default directory where the fi +le to be backed up are located, default is \$HOME Report bugs to <joran\@alice-dsl.de>. EOHELP exit(0); } sub version_and_exit { print <<EOVERSION; $0 $VERSION Copyright (C) 2009 Sven-Thorsten Fahrbach This is free software. You may redistribute copies of it under the ter +ms of the GNU General Public License <http://www.gnu.org/licenses/gpl.html>. There is NO WARRANTY, to the extent permitted by law. Written by Sven-Thorsten Fahrbach. EOVERSION exit(0); }
The corresponding config file looks like this:
# config file for dotbackup # Entries here override default options. # Command line arguments override config-file options # working-directory: default is ${HOME} # working-directory = /home/sven # destination-directory: default is pwd destination-dir = /home/sven/backups # log-file: default is ~/.dotbackup/log # log-file = /home/sven/.dotbackup/log # file-list: list of files to be backed up, default is ~/.dotbackup/fi +les # file-list = /home/sven/.dotbackup/files # tar-options: additional options to be passed to tar, default is unde +f # Use the following argument to exclude .tmp files from the archive # tar-options = --exclude=*.tmp # gzip-options: dito gzip # use the following option to suppress output from gzip: # gzip-options = --quiet # compression-level: compression level for gzip to be used, default is + 1 # use the following option for best compression (might be slow) # compression-level = 9 compression-level = 3 # filename: filename to be used for the tarball, default is dotbackup ++ # timestamp, do not include file suffixes, these are added automatical +ly # filename = mybackup
Here's an example for a file list, each file has to be on its own line.
.bashrc bin .fetchmailrc .forward .irssi .msmtprc .mutt .muttrc .procmail .procmailrc .purple .urlview .vim .vimrc

In reply to Backup script by svetho

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (3)
As of 2024-04-25 19:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found