LoneRanger has asked for the wisdom of the Perl Monks concerning the following question:

There really has to be an easier way to do this. It's for a web site that displays my MP3 collection, this is about 1.5 hours of work. Don't laugh. I'm really not very good at this, YET.
@dirs = qx {ls -R \/HD\/MP3 | grep \:};
foreach $dir (@dirs)
{
    chomp $dir;
    $dir =~ s/://;
    $dir = "\"" . $dir . "\"";
    @files = qx {cd $dir ; ls *.mp3 ; cd \/HD\/MP3\/};
    $dir =~ s/"//g;
    print "

$dir

\n"; foreach $file (@files) { print "
$file
"; } }
I had to use the nested foreach() because I wanted the dir name outdented, I tried indenting the filenames using \t, but I must not know enough to even get that right. Yes I know my tags have spaces in them, that's the only way I could get them to show up on the post.

For it to work you need to replace /HD/MP3 in lines 1 and 7 with a dir containing some MP3s and some album subdirs.

Thanks in advance,
JR Boyens
Self Proclaimed Perl Newbie

Replies are listed 'Best First'.
RE: Easier way to do this
by mwp (Hermit) on Aug 18, 2000 at 01:02 UTC

    Well, there are a couple ways to do it. Here are three:

    • System calls to `ls' and `grep' (like you did).
    • Perl's internal system calls to list files and grep the lists.
    • Use File::Find at the top of a directory tree and look for *.mp3.

    Out of these three, I would definitely recommend File::Find. There is the least chance of something going horribly, horribly wrong. :-) You should read the File::Find manpage, but since you are a self-proclaimed newbie, I'll give you an example!

    #!/usr/bin/perl # enable strict syntax checking and warnings # you should (pretty much) always use these use strict; use warnings 'all'; # include our friendly neighborhood file finder use File::Find; sub list_mp3 { # our regex to check for the .mp3 extension /.*?\.mp3$/ && do { # but we're not done yet! # this `do' block will perform any voodoo and output # you want on files that match the above regex # here's a real simple output: print $File::Find::name, "\n"; # See the File::Find manpage for the different variables to use. # You can obviously dress the output up a lot more than this. } # end the do-block } # end the subroutine # finddepth() is a function from File::Find # we have to tell it A) what function to use, and # B) where to start searching # # this will start at /hd/mp3 and search downward, passing # each file to the list_mp3 function above finddepth(\&list_mp3, '/HD/MP3');

    If you need me to clarify anything, just holler. Hopefully this will be enough to get you started!

    Alakaboo

RE: Easier way to do this
by steveAZ98 (Monk) on Aug 18, 2000 at 01:00 UTC
    Sounds like a job for File::Find. How about something like this?
    #!/usr/bin/perl -w use File::Find; finddepth(\&output, '.'); sub output { return if $File::Find::name !~ /.mp3$/; print "<p>$File::Find::dir</p>\n"; (my $n = $File::Find::name) =~ s/.*\///; print "\t<blockquote>$n</blockquote>\n"; }
    Update:
    I'm not sure what you mean by it's printing out a directory at the top of MP3 name. If you'd like the directory name to print only once then you can save the previous one in a global maybe and then check to see if they match. If they don't match then print the name of the directory. ie.
    #!/usr/bin/perl -w use File::Find; use vars qw($prev); finddepth(\&output, '.'); sub output { return if $File::Find::name !~ /.mp3$/; print "<p>$File::Find::dir</p>\n" if $File::Find::dir ne $prev; $prev = $File::Find::dir; (my $n = $File::Find::name) =~ s/.*\///; print "\t<blockquote>$n</blockquote>\n"; }
    By the way, it's much easier to use finddepth for your recursive search, than to roll your own recursive search.
    HTH
      Unfortunately that outputs a directory at the top of MP3 name. How can I remove that?
RE: Easier way to do this
by KM (Priest) on Aug 18, 2000 at 17:11 UTC
    The other answers suggest File::Find, which is good. But, if you are using mod_perl, you should check out Apache::MP3 which creates browsable directories of MP3 files.

    Cheers,
    KM

Re: Easier way to do this
by deprecated (Priest) on Feb 04, 2001 at 11:57 UTC

    per jeffa's suggestion, this node has a disclaimer.

    THIS NODE CONTAINS NO PERL CODE AND SUGGESTS A SOLUTION OTHER THAN PERL.
    it appears that `sort` below can be broken with embedded newlines.(thanks, tilly) shame on you for using embedded newlines in your mp3 names. :)
    and now, for something completely different...

    Other people have commented on File::Find, which is useful and 100% perl. However, I have also run into this problem and found it to be more shell-esque than perl-esque (at least in my situation, ymmv).

    I use the following shell script, which requires slocate. The good news is it has regex abilities. Soooooo, I wrote this small script which is very useful:

    #!/bin/bash slocate -U /home/mp3 -o /home/mp3/mp3s.sldb ; rm -f /home/mp3/playlist_* ; rm -f /home/httpd/html/playlist/playlist_* ; slocate -d /home/mp3/mp3s.sldb -r '\.[Mm][Pp]3$' | sort > "/home/mp3/ +playlist_`date | cut -c1-3`_`date | awk '{print $4}' | cut -d: -f1,2` +" ; chown alex.wheel /home/mp3/playlist_* ; cp /home/mp3/playlist_* /home/httpd/html/playlist ; chown alex.wheel /home/mp3/mp3s.sldb ;
    its really cut-and-dry, very fast, and does exactly what you want. just slap it in your ~/.crontab or whereever your distro prefers. Note, this assumes you use /home/httpd/html, it might be something like /usr/local/apache/htdocs/html or somesuch. The output of this is available on my homenode. Of course /home/mp3 is probably not where you keep your mp3's either.

    --
    #!/bin/dep

Re: Easier way to do this
by Shendal (Hermit) on Aug 18, 2000 at 01:12 UTC
    Look into File::Find. One of the first things to learn with perl is how to use already existing modules and libraries... no need to reinvent the wheel on this one.

    Something like this is what you'd end up with:
    use strict; use File::Find; use File::Basename; my($topdir) = '/'; # where you want to start from my(@files) = find ( \&wanted, "$topdir" ); foreach (@files) { my($dir) = dirname($_); if ($dir ne $prev_dir) { print "<P>" . $dir . "</P>\n"; $prev_dir = $dir; } print "<BLOCKQUOTE>" . $_ . "</BLOCKQUOTE>\n"; } sub wanted { /\.mp3$/ && -f; }
    Hope that helps,
    Shendal

    Update: As Corion so aptly put, my regex was simply wrong. I fixed it. Serves me right for posting untested code.

      Your reply is the easiest for me to understand, but there seems to be an error in the regexp. I get this response.

      
      Thu Aug 17 21:41:17 2000 index.cgi: /*.mp3$/: ?+*{} follows nothing in regexp at ./index.cgi line 34.
      
      
      So what happenened?? I am no good at regexps, and I've never used them out of m// or s///. I have copied your code exactly three times, I tried to make sense of it, but it has me stumped (surprise, surprise, surprise).

        The regular expression is simply wrong :-). A regular expression that matches everything with the extension .mp3 would be for example :

        /\.mp3$/

        A quick explanation : The \.mp3 part is to ask for a literal dot followed by mp3 and the $ part is that the string must end after this. This is also the shortest regular expression that will match all files with an extension of .mp3.

        Another regular expression, more in the spirit of the faulty RE would have been

        /.*\.mp3$/
        in which you'll recognize the \.mp3$ part from above plus the .* part, which means "match an arbitrary character" (the dot) "zero or more times" (the star).

        As a Perl newbie, getting familiar with regular expressions is quite a task, but you can never start too early with them - in fact, I like Perl as it is the only language I know with REs built into the language core :-)

        Some recommended reading on regular expressions is the RE man page and of course the book "Mastering Regular Expressions" by Jeffrey Friedel - but that one contains more about REs than you ever thought there is to know.