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

I have a somewhat simple program that reads a list of file/dir names from the command line and then attempts to group them in as close to 700MB as possible chunks. This is of course so I can burn as many mp3 folders on one cd as possible.

The command line looks like:
$mp3concat -s 700 `ls`

All is well so long as there are no spaces in the filenames. But as soon as a file has a space in it's name the program goes haywire.

I create my file list with
my (@filelist) = @ARGV;

And then later try to use it with
foreach $file(@filelist) {

The real problem seems to hit when I try
move($file, "disc$disc/$file") or warn "move $file to disc$disc/$file failed: $!\n"

$disc dir has been made at this point and the move works with spaceless names. What do I need to do so that names with spaces also work? Any ideas?

Replies are listed 'Best First'.
Re: filenames with spaces causing problems
by broquaint (Abbot) on Jul 11, 2003 at 12:41 UTC
    Instead of using ls you could just use perl e.g
    use IO::Dir; my @files = grep -f, IO::Dir->new(".")->read;
    That's just one simple approach, whereas you may prefer to expand it using opendir and readdir or even File::Find::Rule.
    HTH

    _________
    broquaint

      Yeah but I don't always want to do every file or directory. I'd rather be able to pass a list as an argument so the program is more flexable.
        Then pass in the directory that you're listing, and better still, use that in conjunction with File::Find::Rule e.g
        use File::Find::Rule; my $dir = shift; die "$0: invalid directory $dir\n" unless -d $dir; my @files = find( name => '*.mp3', in => $dir );
        I think you'll find that you'll be able to get a lot more flexibility and extensibility if you build the file listing into the program (not that there's anything wrong with using command line tools of course).
        HTH

        _________
        broquaint

Re: filenames with spaces causing problems
by dws (Chancellor) on Jul 11, 2003 at 12:51 UTC
    What do I need to do so that names with spaces also work? Any ideas?

    The problem you're having is on the command line. When you do

    mp3concat -s 700 `ls`
    the results of ls become the command line for mp3concat, but if ls encounters filenames with embedded spaces, they'll appear as separate tokens on the command line. By the time these tokens get into @ARGV, it's too late.

    One approach to fixing this is to have mp3concat take directory names as arguments (either instead of or in addition to filenames). Then, within the code, you could do something like

    foreach my $arg ( @ARGV ) { doDir($arg), next if -d $arg; doFile($arg) if -f $arg; } ... sub doDir { my $dir = @_; opendir(D, $dir) or die "$dir: $!"; foreach my $file ( grep { -f } readdir(D) ) { doFile($file); } closedir(D); }
    With a little more work, this can handle hierarchies, as well.

    Another alternative is to use File::Find to traverse directories, looking for .mp3 files. If you search around the monastary, you'll find plenty of examples of using File::Find.

      You guys are all giving good info but let me make a quick clarification of what I'm trying to do. I'm not just moving the individual mp3s. I'm trying to move an entire cd - or entire dir that contains a bunch of mp3s. for example

      $mp3concat -s 700 "Queen/" "RATT/" "R.E.M/" "RobbieWilliams/" "Rolling Stones/" "Rush/"
      or
      mp3concat -s 700 `ls |awk '{print "\""$0"\""}'`

      As long as I quote it on the command line $file seems to have the correct value but the move still doesn't work.
Re: filenames with spaces causing problems
by derby (Abbot) on Jul 11, 2003 at 12:46 UTC
    The real problem is in how the shell interprets the command line. Rather than passing the output of ls to your script (which can fail for very large directories) look into just passing the directory name and then using opendir/readdir or IO::Dir to read and process the contents of the directory. File::Copy's move should then work fine (caveat -- I haven't tried it).

    -derby

    update: dws is correct, it's the output of `ls` that is messing you up. Check your shell documentation for IFS (Internal Field Separator). If you're convinced the backtick approach is what you want, you could set the IFS variable to something other than whitespace (say a comma) and then use something like `ls -m` ... but then you'd have to deal with leading whitespace.

Re: filenames with spaces causing problems
by Abigail-II (Bishop) on Jul 11, 2003 at 13:58 UTC
    For this particular problem:
    $mp3concat -s 700 *

    should do what you want - that will not turn filenames with embedded whitespace into different tokens.

    Abigail

Re: filenames with spaces causing problems
by roju (Friar) on Jul 11, 2003 at 15:12 UTC
    All the above suggestions seem good. Howver, if you're doing complex(ish) things with the ls to only choose certain files, piping find into your script might be better. For instance:
    $ find . -name 'mp3s' | mp3concat -s 700

    Then, in your script, instead of reading @ARGV, just read STDIN.

Re: filenames with spaces causing problems
by Anonymous Monk on Jul 11, 2003 at 12:48 UTC

    Instead of

    #I create my file list with my (@filelist) = @ARGV;

    It might be better to call the program as $mp3concat -s 700 `pwd` and then use:

    my $mp3dir=shift or do { warn "No directory specified, using current directory.\n" ; $mp3dir='.'; }; opendir(MPD,$mp3dir) or die("can't open directory, $mp3dir.\n $!"); my @filelist=readdir(MPD);

    Remember not to use "." and "..", though.

Re: filenames with spaces causing problems
by sgifford (Prior) on Jul 11, 2003 at 16:49 UTC
    As others have said, it's a problem with the shell and not your program. To fix this in the shell, you can use GNU find and xargs:
      find . -type f -print0 |xargs -0 /your/script
    
    It uses NULLs to terminate filenames instead of spaces.

    Maybe not exactly what you want, but may help.

Re: filenames with spaces causing problems
by tedrek (Pilgrim) on Jul 11, 2003 at 16:21 UTC

    how do you actually move the files? if you are using a shell command to do move them, it is possible that you are running into the same token problem as people have pointed out for your input. Could you please post your 'move' sub to help find the problem.

      sub mv_files{ my ($file,$disc)=@_; print "moving $file to disc$disc/$file\n"; if ($doit == 1) { print "truely moving $file to disc$disc/$file\n"; move($file, "disc$disc/$file") or warn "move $file to +disc$disc/$file failed: $!\n" } }
        As many of the posts have been mentioned, using `ls`, files with spaces will be split into multiple tokens. If you use any of the suggestions from above to actually read in the lists of files to use (ie, do it from in perl, use * globbing, use find), then $file will contain the correct data.

        On this line,

        move($file, "disc$disc/$file") or warn "move $file to +disc$disc/$file failed: $!\n"

        What error is it printing out, and where does move() come from, ie. which module. Or if you wrote it your self what does sub move {.... look like?