Jean-Philippe Ouellet Wrote:
Unfortunately, that solution is insufficiently versatile for my day-to-day needs, in which I frequently need to copymerge the *contents* of one directory into the *contents* of another. Using cp requires that a) the two folders being merged have exactly the same names and b) that you specify the parent of the destination as the destination path. For me, (a) is frequently untrue, and (b) is always inconvenient.
Here is the Perl script I adapted from source I found online. I use it all the time for my purposes:
Code:
usage: mergedirs.pl [--no_overwrite | --verbose] source_path destination_path
It causes all the contents of source_path to be merged into the contents of destination_path. The root directory names need not match. There are global variables within the script to ignore UNIX invisible files, because I need to use this with version control systems.
Code:
#!/usr/bin/perl -w
# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# Directory Merging Script
# Version 1.0
# version 1.1 Allen Smith. added overwriting files and leaving source intact
#
# Copyright (c) 2002 by Ian Hickson
#
# 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 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
use strict;
use File::Copy;
# Global control options
my $verbose = 0; # print lots of error messages
my $overwriteFiles = 1; # overwrite files in destination
my $leaveSourceIntact = 1; # whether to do a copy or a move merge
my $ignoreInvisibles = 1; # ignore Unix inivisible files (. prefix) in source
# takes two arguments: sourcePath, destinationPath
# recursively descend through sourcePath, moving stuff that doesn't exist in destinationPath into it
my $argumentsAreValid = 0;
if (@ARGV >= 2)
{
my $counter = 0;
# Test for option switches first.
while( $ARGV[$counter] =~ m/^--/ )
{
if( $ARGV[$counter] =~ m/--no_overwrite/ )
{
$overwriteFiles = 0;
}
elsif( $ARGV[$counter] =~ m/--verbose/ )
{
$verbose = 1;
}
else
{
print STDERR "Unexpected option " . $ARGV[$counter] . "\n";
}
# advance to the next switch
$counter += 1;
}
# The remaining arguments are the paths
if(@ARGV == $counter + 2)
{
$argumentsAreValid = 1;
merge($ARGV[$counter], $ARGV[$counter + 1]);
}
}
# Invalid arguments. Print an error.
if($argumentsAreValid == 0)
{
print STDERR "usage: mergedirs.pl [--no_overwrite | --verbose] source_path destination_path\n";
}
#========== merge ==============================================================
#
# Purpose: Recursively merges the source path into the destination,
# affected by the global settings flags.
#
# Parameters: $_[0] = the source path
# $_[1] = the destination path
#
#===============================================================================
sub merge
{
my($sourcePath, $destinationPath) = @_;
my $success = 0;
# SOURCE: not -e -f -d empty -d else
# TARGET:
# not -e err mv mv mv err
#
# -f err cp err err err
#
# -d err err loop rmdir err
#
# else err err err err err
print "merge: merging $sourcePath and $destinationPath\n" if $verbose;
if( $sourcePath =~ /^\./ and $ignoreInvisibles == 1 )
{
print "merge: ignoring invisible $sourcePath\n" if $verbose;
}
elsif (not -e $sourcePath)
{
print STDERR "merge:$sourcePath: doesn't exist\n";
}
elsif (not (-f $sourcePath or -d $sourcePath))
{
print STDERR "merge:$sourcePath: not a normal file\n";
}
else
{
# If current path is a directory, and the destination does not exist, create a
# new empty destination directory. The recursive calls below will then fill it in.
if(-d $sourcePath and not -e $destinationPath)
{
mkdir($destinationPath);
}
#---------- Do the real copy now.
# b does not already exist. Just copy the file.
if(not -e $destinationPath)
{
print "merge: moving $sourcePath to $destinationPath\n" if $verbose;
if($leaveSourceIntact == 1)
{
$success = copy($sourcePath, $destinationPath);
}
else
{
$success = move($sourcePath, $destinationPath);
}
if($success == 0)
{
print STDERR "merge:$sourcePath: could not move to $destinationPath, $!\n";
}
}
# b exists and is a file
elsif(-f $destinationPath)
{
# overwrite existing file in the second directory!
if($overwriteFiles == 1)
{
print "merge: overwriting $destinationPath with $sourcePath\n" if $verbose;
if($leaveSourceIntact == 1)
{
$success = copy($sourcePath, $destinationPath);
}
else
{
$success = move($sourcePath, $destinationPath);
}
if($success == 0)
{
print STDERR "merge:$sourcePath: could not overwrite to $destinationPath, $!\n";
}
}
}
# b is a directory
elsif(-d $destinationPath)
{
# make sure a is also a directory
if (-d $sourcePath)
{
my @entries = getdir($sourcePath);
if (@entries)
{
# not empty
# recurse through it to give us a chance to make it empty
print "merge: going through contents of $sourcePath\n" if $verbose;
foreach my $entry (@entries) {
my $c = "$sourcePath/$entry";
$c =~ s|//|/|gos;
my $d = "$destinationPath/$entry";
$d =~ s|//|/|gos;
&merge($c, $d);
}
}
# empty now?
if(not $leaveSourceIntact)
{
@entries = getdir($sourcePath);
if (not @entries)
{
print "merge: deleting empty directory $sourcePath\n" if $verbose;
rmdir($sourcePath) or print STDERR "merge:$sourcePath: could not delete directory, $!\n";
} else {
print STDERR "merge:$sourcePath: could not delete directory, directory is not empty\n";
}
}
}
else {
print STDERR "merge:$sourcePath: conflicts with directory $destinationPath\n";
}
}
else
{
print STDERR "merge:error\n";
}
}
}
sub getdir {
my($sourcePath) = @_;
local *DIR;
unless (opendir(DIR, $sourcePath)) {
print STDERR "merge:$sourcePath: can't open directory\n";
return;
}
my @entries;
while (my $entry = readdir(DIR)) {
if ($entry !~ m/^\.\.?$/o) {
push(@entries, $entry);
}
}
closedir(DIR) or print STDERR "merge:$sourcePath: could not close directory, $!\n";
return @entries;
}