Image of the glider from the Game of Life by John Conway
Skip to content

Simple Recursion in Perl

I'm going to embarrass myself today, and post some hack-ish Perl code using recursion. Actually, because the code is only 35 lines, I think it turned out actually fairly well, but as I am a Perl n00b, I am sure that the experts out there will disagree with the quality of the code.

First and foremost, I have a global variable used as a counter. I know that this is a BIG NO-NO, but, because the code is only 35 lines in length, I'm not too worried. I do recognize, though, that passing the counter by reference through the recursive function PrintFiles() would probably be the better way to handle that, but then, that's just making things a bit more difficult than I care at this point.

What does the program do? It should be fairly obvious: it runs through a root directory structure, looking for OGG Vorbis files, counting them, and printing the results to another text file for inventory. Basic, gets the job done, and without a lot of fanfare. The code should be self-evident, but I'm anal about commenting code, so redundant comments are placed throughout. The program was just an exercise for me to:

  1. Increase my ability to do simple recursion.
  2. Learn recursion using Perl.
  3. Create an inventory of my music.

Anyway, here's the code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/perl -w

open(OUT, ">oggfiles.txt"); # Create the file to read to
$counter = 0;   # Global variable (no-no)!! :)

sub PrintFiles  # Recursive function for printing dir contents
{
    my ($indent) = @_;  # Keep track of indentation
    opendir(MD, "./");  # Start looking through local directory
   
    foreach my $file (sort readdir MD)  # Start a loop going through each dir sorted
    {
        unless($file =~ m/^\./ || $file =~ m/^ogg/) # Prevent an infinite loop, don't worry about hidden (.*), oggdir.pl and oggfiles.txt files
        {
            $counter++ if $file =~ m/\.ogg$/;   # Keep a counter for every ogg file
           
            print OUT $indent . $file . "\n";   # Print dir contents to a file
           
            if (-d $file)   # If a directory
            {
                chdir $file;    # Change to the directory
                PrintFiles("$indent  ");    # Call the recursive function again, incrementing the ident
                chdir "../";    # Change back
            }
        }
    }
   
    closedir(MD);   # Close the dir
}

PrintFiles("");     # First call of the recursive function, with no spaces for indentation

print OUT "\nCurrent number of music files: " . $counter . "\n\n";

close(OUT); # Close file written to

{ 4 } Comments

  1. Harley Pig using Firefox 2.0.0.1 on GNU/Linux 64 bits | January 21, 2007 at 7:14 am | Permalink

    When I ran this I got a file full of data that did *not* include ogg files.

    I spent more time than I should have trying to figure out why this was. I never did figure it out. I'm assuming you were able to make the code work the way you wanted it to. From what I can tell though, it looks ok. A global counter variable in this circumstance isn't really a bad thing.

    I know you're doing this to learn recursion, at least a better understand of it in perl, but I can't just let this pass without pointing out File::Find.

    File::Find will take care of most of the heavy lifting for you.

    Some notes:

    Let the shell do the hard work. Why take the trouble to open a file when
    you can redirect it? Call it like

    1
    inventory.pl > oggfiles.txt

    This bit of code prints out every single directory name, regardless of
    whether or not there is an ogg file in it or its children. You'd need to
    store the data and then filter it once all the data was gathered.

    The 'preprocess' portion of the code is called after each readdir with the
    file list passed in. This allows us to set up the indent variable and
    filter out the 'hidden' directories.

    The 'postprocess' portion of the code is called after each directory has
    been processed (i.e., the list returned by preprocess has been iterated
    through). This allows us to trim the indent variable.

    The 'wanted' portion of the code is called for every single file found in
    the tree.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #!/usr/bin/perl -w

    use strict;

    use File::Find;

    my $indent = '';

    find({
      'wanted' => sub { print "$indent$_\n" if /\.ogg$/i; },

      'preprocess' => sub {
        print "$indent$_\n";
        $indent = "$indent  ";
        sort grep { ! /^\./ } @_;
      },

      'postprocess' => sub { $indent = substr( $indent, 0, -2 ); },

    }, '.');
  2. Aaron using Firefox 2.0.0.1 on Ubuntu | January 21, 2007 at 8:33 am | Permalink

    I'll have to give that bit of code a look over when I get to work. File::Find looks like it will do exactly what I need.

    I don't know why you were getting files that were everything but *.ogg files. Looking over the code, everything is perfect. Maybe I'm missing something from the copy/paste.

  3. Sam using Firefox 1.5.0.7 on Debian GNU/Linux | January 23, 2007 at 7:00 pm | Permalink

    oh, you know you want to just use:

    1
    find . -name "*.ogg"

    :)

  4. Chris Blanc using Internet Explorer 6.0 on Windows XP | December 10, 2007 at 12:45 pm | Permalink

    I've unleashed a script inspired by your approach to recursivity here:

    http://www.chrisblanc.org/blog/?p=77

    It's a simple linefeed filter. By the way, don't OGGs sound better than other file formats at the same bitrate? Amazing compression.

{ 2 } Trackbacks

  1. [...] took my Perl script that I wrote some time ago, and rewrote it in Python. The exercise of the initial Perl script, was to get a better handle on [...]

  2. [...] took my Perl script that I wrote some time ago, and rewrote it in Python. The exercise of the initial Perl script, was to get a better handle on [...]

Post a Comment

Your email is never published nor shared.

Switch to our mobile site