#!/usr/bin/perl -w ######################################################################### # # # Synchronise one directory with another # # Copyright (C) 2002-09, John Zaitseff # # # ######################################################################### # Author: John Zaitseff # Date: 13th May, 2009 # Version: 1.11 # This script synchronises one directory with another by copying and # possibly deleting files. The directories may be on a local or a remote # system. It uses the RSYNC program over SSH (Secure Shell) to do this # efficiently and securely. This script, "insync", is based on "cphome". # # Syntax: # insync [options] src dest - synchronise dest with src # # where the options may include one or more of: # # -n - performs a dry run (does not actually do the operation), # -d - forces extra files in the destination directory to be # deleted, # -O - forces newer files in the destination directory to be # overwritten (dangerous!), # -H - takes the additional effort needed to preserve hard links. # # Both "dest" and "src" may either be a local path or a remote hostname # and path, separated by a colon. Options may be combined in the standard # UNIX fashion. # # The main reason for using "insync" instead of "rsync" directly is that # the options are much easier to remember with "insync"! # # # Examples: # # insync /home/john/data/current /zip/data/2002 # Synchronise by copying all files in the local source directory # "/home/john/data/current" to the directory "/zip/data/2002". Any # files in "/zip/data/2002" that are not in the source directory are # ignored. Newer files in the destination directory are NOT # overwritten. # # insync -d /home/john/data/current /zip/data/2002 # Same as the above example, except that any files in the destination # directory "/zip/data/2002" that are not in "/home/john/data/current" # ARE deleted. # # insync /data/cvs zap.org.au:/data/cvs # Copy all files from the directory "/data/cvs" to the destination # directory "/data/cvs" on the host "zap.org.au". Any files in # "/data/cvs" at "zap.org.au" that are not present in the (local) # source directory are ignored. Newer files in the destination # directory are not overwritten. # # insync -n /data/cvs zap.org.au:/data/cvs # Same as the above, but don't actually perform the operation: just # list what would be done. # # insync -d altair.zap.org.au:/data/john/src /data/john/altair/src # Copy all files from the "/data/john/src" directory on the host # "altair.zap.org.au" to the local directory "/data/john/altair/src". # Any files in the destination directory that are not present on the # remote host ARE deleted. Newer files are not overwritten. # This program is free software. You may distribute it and/or modify it # under the terms of the GNU General Public License as published by the # Free Software Foundation; either Version 2 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, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ######################################################################### # Configuration parameters and default values use strict; # Enforce better programming habits (our $O = $0) =~ s,^.*/,,; # Script name (without path) our $version = "1.11"; # Script version # Programs and options needed for this script to function our $ssh_prog = "ssh"; # SSH binary our $rsync_prog = "rsync"; # RSYNC binary our $rsync_opts = "-vazS"; # Default options to RSYNC our $rsync_dry = "-n"; # Dry-run options our $rsync_del = "--delete"; # Delete options our $rsync_noovwt = "-u"; # Option if Overwrite is NOT requested our $rsync_hlinks = "-H"; # Hard link preserve options # Environment variables $ENV{PATH} = "/bin:/usr/bin:/usr/local/bin"; delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; # Function prototypes sub showusage(); sub showversion(); sub showcmdlerr(@); sub findprog($); sub checkparam($); sub rsync($); ######################################################################### # Initialise global variables our $opt_dry = 0; # Perform a dry run? our $opt_del = 0; # Delete extra files? our $opt_ovwt = 0; # Overwrite newer files? our $opt_hlinks = 0; # Preserve hard links? our $src; # Source our $dst; # Destination ######################################################################### # Main program findprog($rsync_prog); findprog($ssh_prog); $rsync_opts .= " -e $ssh_prog"; # Use SSH with RSYNC # Process command line arguments while ($_ = $ARGV[0]) { last if (! /^-/); shift @ARGV; last if ($_ eq '--'); # Split combined short-form options into single arguments if (/^-(\w{2,})/) { my @args = split //, $1; foreach my $arg (@args) { $arg = "-$arg"; } unshift @ARGV, @args; next; } # Process command-line options if (($_ eq "--dry-run") || ($_ eq "-n")) { $opt_dry = 1; } elsif (($_ eq "--delete") || ($_ eq "-d")) { $opt_del = 1; } elsif (($_ eq "--overwrite") || ($_ eq "-O")) { $opt_ovwt = 1; } elsif (($_ eq "--hard-links") || ($_ eq "-H")) { $opt_hlinks = 1; } elsif (($_ eq "--help") || ($_ eq "-h") || ($_ eq "-?")) { showusage(); } elsif (($_ eq "--version") || ($_ eq "-V")) { showversion(); } else { showcmdlerr("Unrecognised option: $_"); } } showcmdlerr("Missing parameters") if $#ARGV < 1; showcmdlerr("Too many parameters") if $#ARGV > 1; # Set various RSYNC options $rsync_opts .= " $rsync_dry" if $opt_dry; $rsync_opts .= " $rsync_del" if $opt_del; $rsync_opts .= " $rsync_hlinks" if $opt_hlinks; $rsync_opts .= " $rsync_noovwt" if ! $opt_ovwt; # Set source and destinations directories $src = $ARGV[0]; checkparam($src); $dst = $ARGV[1]; checkparam($dst); # Final sanity checks... if ($src eq $dst) { die "$O: Source and destination are the same\n"; } if (($src =~ /:/) && ($dst =~ /:/)) { die "$O: Both source and destination are remote\n"; } # Perform the actual operation rsync("$rsync_prog $rsync_opts $src/ $dst/"); ######################################################################### # Display usage information and terminate sub showusage() { print <<"DATAEND" $O v$version: Synchronise one directory with another. Copyright (C) 2002-09, John Zaitseff. This script synchronises one directory with another by copying and possibly deleting files. The directories may be on a local or a remote system. It uses the RSYNC program over SSH (Secure Shell) to do this efficiently and securely. This script, \"$O\", is based on \"cphome\". Syntax: $O [options] src dest - synchronise dest with src where the options may include one or more of: -n - performs a dry run (does not actually do the operation), -d - forces extra files in the destination directory to be deleted, -O - forces newer files in the destination directory to be overwritten (dangerous!), -H - takes the additional effort needed to preserve hard links. Both \"dest\" and \"src\" may either be a local path or a remote hostname and path, separated by a colon. Options may be combined in the standard UNIX fashion. For more information, please read the source code of the script. This may be found in $0. DATAEND ; exit(0); } ######################################################################### # Display program version information and terminate sub showversion() { print <<"DATAEND" $O v$version: Synchronise one directory with another. Copyright (C) 2002-09, John Zaitseff. This program, including associated files, is distributed under the GNU General Public License. See the file COPYING for more information. DATAEND ; exit(0); } ######################################################################### # Show an error message relating to the command-line and terminate sub showcmdlerr(@) { map { warn "$O: $_\n" } @_; die "\nUsage:\n" . " $O [-dnOH] src dest - to synchronise dest with src\n" . " $O --help - to display more help\n"; } ######################################################################### # Find location of programs sub findprog ($) { my $prog = $_[0]; my @path = split(/:/,$ENV{PATH}); foreach my $pp (@path) { if ( -x "$pp/$prog" ) { $_[0] = "$pp/$prog"; # Update parameter to include path return; } } die "$O: $prog: Could not find executable in $ENV{PATH}\n"; } ######################################################################### # Check command line parameter sub checkparam ($) { my $param = $_[0]; if ($param =~ /:/) { # Parameter has a colon in it $param =~ /(.*?):(.*)/; die "$O: Missing remote hostname parameter\n" if (! $1); die "$O: Missing remote directory parameter\n" if (! $2); } else { # Parameter does not have a colon: treat as a local directory die "$O: Empty parameter\n" if (! $param); die "$O: Cannot access $param: $!\n" if (! -r $param); } # We want the following changes to propagate back to the caller... if ($_[0] =~ /^(.*?)\/+$/) { $_[0] = $1; # Remove trailing slashes if (! $_[0]) { # An empty result was a single slash $_[0] = "/"; } } } ######################################################################### # Perform RSYNC operation sub rsync ($) { my $cmdline = $_[0]; open(RSYNC, "$cmdline |") || die "$O: Cannot execute rsync: $!\n"; # Filter unwanted output while () { chomp; if (! /^building file list ... done$/ && ! /^receiving file list ... done$/ && ! /^wrote \d+ bytes\s+read \d+ bytes\s+.+sec$/ && ! /^sent \d+ bytes\s+received \d+ bytes\s+.+sec$/ && ! /^total size is \d+\s+speedup is .+$/ && ! /^rsync: open connection using / && ! /^rsync: building file list...$/ && ! /^rsync: \d+ files to consider.$/ && ! /^receiving incremental file list$/ && ! /^sending incremental file list$/ && ! /^\s*$/) { print "$_\n"; } } close(RSYNC); }