#!/usr/bin/perl

# splitdir:
# This script moves the files from a specified directory
# into several sequentially named sub-directories.
# (This is intended for splitting up large directories.)
# The first command-line argument specifies the directory to be split up.
# The second command-line argument specifies the basename to be used for the
# sequence of sub-directories.
# (If no 2nd command-line argument is given, subDirBasename will be "subdir")
# The third command-line argument specifies the maximum number of files to
# be put in each sub-directory.
# (If no 3rd command-line argument is given, maxFiles will be 100)
# Sample usage:
# splitdir ~/MyStuff stuff 200
# The above command would create sub-directories "stuff1", "stuff2", ...
# and move the files (but not existing sub-directories) from ~/MyStuff
# into those sub-directories in groups of 200.
# If a sub-directory with one of those names already exists, that
# sub-directory will be left as is and the next name in the sequence
# used instead.
#
# Cameron Hayne (macdev@hayne.net)  March 2009

use strict;
use warnings;

my $scriptName = "splitdir";  # used in error messages

sub fatalError($)
{
    my ($msg) = @_;
    print "$scriptName: $msg\n";
    exit 1;
}

MAIN:
{
    die "Usage: $scriptName dirName [subDirBasename [maxFiles]]\n"
         if scalar(@ARGV) < 1;
    my $dirName = shift @ARGV;
    fatalError("'$dirName' is not a directory")
          unless -d $dirName;
    fatalError("Insufficient permissions on '$dirName'")
          unless -r $dirName and -w $dirName and -x $dirName;
    my $subDirBasename = (@ARGV ? shift @ARGV : 'subdir');
    fatalError("'$subDirBasename' is not an acceptable subDirBasename")
          unless $subDirBasename =~ /^[\w -]+$/;
    my $maxFiles = (@ARGV ? shift @ARGV : 100);
    fatalError("maxFiles must be a positive integer")
          unless $maxFiles =~ /^\d+$/ and $maxFiles > 0;
    
    # cd to the specified directory to make things easier
    chdir($dirName) or fatalError("Can't chdir to $dirName: $!");
    
    my $fileCount = 0;
    my $subDirCount = 0;
    my $subDirNumber = 0;
    my $subDirName;
    opendir(DIR, '.') or fatalError("Can't opendir $dirName: $!");
    while (defined(my $fileName = readdir(DIR)))
    {
        next if $fileName =~ /^\.\.?$/;     # skip . and ..
        next if -d $fileName;               # skip sub-directories
        
        if ($fileCount % $maxFiles == 0)
        {
            do
            {
                ++$subDirNumber;
                $subDirName = "$subDirBasename$subDirNumber";
            } while (-e $subDirName);
            
            mkdir $subDirName
                or fatalError("Can't create sub-directory '$subDirName': $!");
            ++$subDirCount;
        }
        
        # since we just created the sub-dir, there should be no possibility
        # of filename collision - but let's be paranoid:
        fatalError("There is already a file named '$fileName' - aborting")
            if -e "$subDirName/$fileName";
        
        rename($fileName, "$subDirName/$fileName")
                or fatalError("Can't move file '$fileName' to " .
                              "subdir '$subDirName': $!");
        ++$fileCount;
    }
    closedir(DIR);
    print "Moved $fileCount files into $subDirCount sub-directories\n";
}

# testing the script in Bash:
# % mkdir LargeDir
# % (cd LargeDir; for i in `jot 1000 1`; do touch $i; done)
# % ls LargeDir | wc -l
#     1000
# % splitdir LargeDir foo 100
# Moved 1000 files into 10 sub-directories
# % ls LargeDir
# foo1	foo10	foo2	foo3	foo4	foo5	foo6	foo7	foo8	foo9

