Jose Faeti
Jose Faeti

Reputation: 12294

How to create the next file or folder in a series of progressively numbered files?

Sorry for the bad title but this is the best I could do! :D

I have a script which creates a new project every time the specified function is called.

Each project must be stored in its own folder, with the name of the project. But, if you don't specify a name, the script will just name it "new projectX", where X is a progressive number.

With time the user could rename the folders or delete some, so every time the script runs, it checks for the smallest number available (not used by another folder) and creates the relevant folder.

Now I managed to make a program which I think works as wanted, but I would like to hear from you if it's OK or there's something wrong which I'm unable to spot, given my inexperience with the language.

    while ( defined( $file = readdir $projects_dir ) )
    {
        # check for files whose name start with "new project"
        if ( $file =~ m/^new project/i )
        {
            push( @files, $file );
        }
    }

    # remove letters from filenames, only the number is left
    foreach $file ( @files )
    {
        $file =~ s/[a-z]//ig;
    }

    @files = sort { $a <=> $b } @files;

    # find the smallest number available
    my $smallest_number = 0;

    foreach $file ( @files )
    {
        if ( $smallest_number != $file )
        {
            last;
        }
        $smallest_number += 1;
    }

    print "Smallest number is $smallest_number";

Upvotes: 1

Views: 480

Answers (4)

FMc
FMc

Reputation: 42421

Here's a basic approach for this sort of problem:

sub next_available_dir {
    my $n = 1;
    my $d;
    $n ++ while -e ($d = "new project$n");
    return $d;
}

my $project_dir = next_available_dir();
mkdir $project_dir;

If you're willing to use a naming pattern that plays nicely with Perl's string auto-increment feature, you can simplify the code further, eliminating the need for $n. For example, newproject000.

Upvotes: 3

Zaid
Zaid

Reputation: 37146

Nothing wrong per se, but that's an awful lot of code to achieve a single objective (get the minimum index of directories.

A core module, couple of subs and few Schwartzian transforms will make the code more flexible:

use strict;
use warnings;
use List::Util 'min';

sub num { $_[0] =~ s|\D+||g } # 'new project4' -> '4', 'new1_project4' -> '14' (!)

sub min_index {

    my ( $dir, $filter ) = @_;

    $filter = qr/./ unless defined $filter; # match all if no filter specified

    opendir my $dirHandle, $dir or die $!;

    my $lowest_index = min                 # get the smallest ...
                        map  { num($_)   }  # ... numerical value ...
                         grep {    -d     }  # ... from all directories ...
                          grep { /$filter/ }  # ... that match the filter ...
                           readdir $dirHandle; # ... from the directory contents
    $lowest_index++ while grep { $lowest_index == num( $_ ) } readdir $dirhandle;
    return $lowest_index;
}

# Ready to use!

my $index = min_index ( 'some/dir' , qr/^new project/ );

my $new_project_name = "new project $index";

Upvotes: 0

Jonathan Leffler
Jonathan Leffler

Reputation: 754030

I think I would use something like:

use strict;
use warnings;

sub new_project_dir
{
    my($base) = @_;
    opendir(my $dh, $base) || die "Failed to open directory $base for reading";
    my $file;
    my @numbers;
    while ($file = readdir $dh)
    {
        $numbers[$1] = 1 if ($file =~ m/^new project(\d+)$/)
    }
    closedir($dh) || die "Failed to close directory $base";
    my $i;
    my $max = $#numbers;
    for ($i = 0; $i < $max; $i++)
    {
        next if (defined $numbers[$i]);
        # Directory did not exist when we scanned the directory
        # But maybe it was created since then!
        my $dir = "new project$i";
        next unless mkdir "$base/$dir";
        return $dir;
    }
    # All numbers from 0..$max were in use...so try adding new numbers...
    while ($i < $max + 100)
    {
        my $dir = "new project$i";
        $i++;
        next unless mkdir "$base/$dir";
        return $dir;
    }
    # Still failed - give in...
    die "Something is amiss - all directories 0..$i in use?";
}

Test code:

my $basedir = "base";
mkdir $basedir unless -d $basedir;

for (my $j = 0; $j < 10; $j++)
{
    my $dir = new_project_dir($basedir);
    print "Create: $dir\n";
    if ($j % 3 == 2)
    {
        my $k = int($j / 2);
        my $o = "new project$k";
        rmdir "$basedir/$o";
        print "Remove: $o\n";
    }
}

Upvotes: 1

shawnhcorey
shawnhcorey

Reputation: 3601

Try this:

#!/usr/bin/env perl

use strict;
use warnings;

# get the current list of files
# see `perldoc -f glob` for details.
my @files = glob( 'some/dir/new\\ project*' );

# set to first name, in case there are none others
my $next_file = 'new project1';

# check for others
if( @files ){

  # a Schwartian transform
  @files = map  { $_->[0]                                     }  # get original
           sort { $a->[1] <=> $b->[1]                         }  # sort by second field which are numbers
           map  { [ $_, do{ ( my $n = $_ ) =~ s/\D//g; $n } ] }  # create an anonymous array with original value and the second field nothing but digits
           @files;

  # last file name is the biggest
  $next_file = $files[-1];

  # add one to it
  $next_file =~ s/(.*)(\d+)$/$1.($2+1)/e;
}

print "next file: $next_file\n";

Upvotes: 0

Related Questions