Iterating over an array of directories and performing mkdir on each one in a special way

Viewed 194

I have an array of directories. I want to iterate over the array and perform mkdir on it:

foreach my $dir (@dirs) {
    print $fh "mkdir -p $dir\n";
}

I want to reduce the amount of chars in the file. So I want to use the special case of mkdir which allows to use {name,name,name} and it will create all of them. So for example, instead of:

mkdir -p /a/b/c/e/f
mkdir -p /a/b/c/e/g
mkdir -p /a/b/c/e/h

It will do:

mkdir -p /a/b/c/e/{f,g,h}

The dirs are already in their canonical mode so realpath is not required here.

What would be the best way to iterate over the array @dirs and do this? I think I need to use dirname on each one and then collect every "collection" and then do mkdir on it. But what is the best way to do it?

2 Answers

It can be done by two loops:

use strict;

# your Filehandle
my $fh;
my @dirs = qw" /a/b/c/e/f
               /a/b/c/e/g
               /a/b/c/e/h ";

# use a hash for temporary hold the dirs per path
my %split_dirs;

for (@dirs) {
    # Split the current path into the path (..../) and the dir behind.
    my ($current_path, $current_dir) = /^(.*\/)(.*)$/;
    # if there where already a dir for this path, add this one and go
    # to the next iteration else there would be created a new one.
    push(@{$split_dirs{$current_path}}, $current_dir);
}

# Now you can join the values per path and print out.
print $fh "mkdir -p $_\{" .
          join(",", @{$split_dirs{$_}}) .
          "\}\n"
    for (keys %split_dirs);

Or a little more compact:

# ...

my %split_dirs;

/^(.*\/)(.*)$/,
    push(@{$split_dirs{$1}}, $2)
    for (@dirs);

print $fh "mkdir -p $_\{" .
          join(",", @{$split_dirs{$_}}) .
          "\}\n"
    for (keys %split_dirs);

Output

mkdir -p /a/b/c/e/{f,g,h}

This is what Compress::BraceExpansion was designed for:

My initial motivation to write this module was to compress the number of characters that are necessary to display a list of server names, e.g. to send in the subject of a text message to a pager/mobile phone. If I start with a long list of servers that follow a standard naming convention, e.g.:

app-dc-srv01 app-dc-srv02 app-dc-srv03 app-dc-srv04 app-dc-srv05
app-dc-srv06 app-dc-srv07 app-dc-srv08 app-dc-srv09 app-dc-srv10

After running through this module, they can be displayed much more efficiently on a pager as:

app-dc-srv{0{1,2,3,4,5,6,7,8,9},10}

The algorithm can also be useful for directories:

/usr/local/{bin,etc,lib,man,sbin}

Be aware of the present limitations though:

The current algorithm is pretty ugly, and will only compress strings that start and/or end with similar text. I've been working on a new algorithm that uses a weighted trie.

Related