perlactivestate

Using Perl glob with spaces in the pattern


I am trying to zip files from a directory. It works well except when the file name has spaces.

Since glob splits its parameter on spaces, I also tried bsd_glob but it did not work.

How do I handle spaces in the file names? I am seeking to retrieve all files.

#Directory of focus
my $log = 'C:/Users/me/Desktop/log';

my @files = bsd_glob( $log.'/*.*' );

#Copy contents to new directory to be zipped
foreach my $file (@files) {
   copy($file, $logout) or die
   "Failed to copy $file: $!\n";
}

Fail to copy

# Create Child tmp
my $out = 'C:/Users/me/Desktop/out';
mkdir $out;

# Directory of focus
my $log = 'C:/Users/me/Desktop/log';

opendir (DIR, $log) or die $!;

while ( my $file = readdir(DIR) ) {
    next if $file =~ /^\./;
    #print "$file\n";
    copy($file, $out) or die "Failed to copy $file: $!\n";
}

closedir (DIR);

Solution

  • There isn't any conflict in your code, as spaces won't matter in the files that glob finds, only in the pattern that you pass to it as a parameter. I notice that you write in a comment on Matt Jacob's post

    I'm sorry, the process works. Thank you! Apparently the file is opened elsewhere

    so I imagine that that was the problem all along. But I thought it would be useful if I explained how to get glob to cope with a pattern that contains spaces

    Behaviour of glob with spaces

    I would write

    my @files = glob "$log/*.*"
    

    because I think it is clearer, but the string you're passing to glob is C:/Users/me/Desktop/log/*.* which has no spaces, so glob is fine

    If you had a space in the path somewhere then you're right - glob would split at those spaces and treat each part as a separate parameter. Say you had

    my @files = glob "C:/Program Files/*"
    

    then you would get the list ('C:/Program') because glob checks whether a file exists only if there is a wildcard in the pattern. So we get back the first part C:/Program which doesn't have a wildcard, but the second part contributes nothing more because there are no files matching Files/*

    Solution using quotes

    The solution in this case is to wrap patterns that contain spaces in a pair of quotation marks - either single or double. So either of

    my @files = glob "'C:/Program Files/*'"
    

    or

    my @files = glob '"C:/Program Files/*"'
    

    will work fine. But if you want to interpolate a path like your C:/Users/me/Desktop/out then the outermost quotes must be double quotes. In your case that would look like

    my $log = 'C:/Users/me/Desktop/log';
    my @files = glob "'$log/*.*'";
    

    but I prefer to use the alternative qq operator like this

    my $log = 'C:/Users/me/Desktop/log';
    my @files = glob qq{"$log/*.*"};
    

    Solution using bsd_glob

    The alternative, as you point out in your question, is to add

    use File::Glob 'bsd_glob'
    

    to the top of your code and use the bsd_glob function instead, which treats spaces in the pattern the same as any other character and doesn't split on them.

    Or if you have

    use File::Glob ':bsd_glob'
    

    (note the additional colon) then the standard glob call will behave the same way as bsd_glob, which allows you to use the angle bracket form of glob like this

    my @files = <C:/Program Files/*>
    

    without any problems