Using Perl glob with spaces in the pattern
Asked Answered
B

2

6

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);
Blastocoel answered 27/8, 2015 at 22:31 Comment(0)
F
7

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

Foreshorten answered 28/8, 2015 at 2:32 Comment(0)
E
1

Don't use glob. Use readdir instead (or File::Find if you need recursion).

opendir (my $dh, $log) or die $!;

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

closedir($dh);
Euthenics answered 27/8, 2015 at 22:40 Comment(12)
Ok great, looks like I can also iterate with readdir. Thanks, Matt.Blastocoel
Just remember to filter out dots (. and ..) and check that entries are files. The documentation I linked to has examples of doing those things.Euthenics
I was able to filter out hidden files and get the correct count, however when i copy(file, outputloc) it fails to copy. Do you know why?Blastocoel
Did you prepend the directory to form a full path?Euthenics
I will make an edit to show. One minute. Thank you very much, I really appreciate the helpBlastocoel
@almond29 Updated my answer with your code fixed in a couple places (lexical directory handle, copying using the full path).Euthenics
great, works but it still is unable to copy the file that has spaces. Which brings me back to where I was at with glob. HmmBlastocoel
I don't see it, but I'm assuming you're using the File::Copy module?Euthenics
Correct, I have that in the headerBlastocoel
I'm sorry, the process works. Thank you! Apparently the file is opened elsewhere.Blastocoel
I wish you would explain "Don't use glob. Use readdir instead (or File::Find if you need recursion)". What you are recommending is "don't debug it - just try a different way", and I think it's a dreadful response to the problem. glob is most often a better solution as it avoids having to discard the pseudo-directories . and .., and returns full file paths which removes the need to rebuild them using the base directory and the names returnedForeshorten
Lets not downvote this. The fact that glob splits on whitespace by default and needs extra quotes is horrible. No other part in Perl behaves like that, so this is just unexpected and will easily lead to bugs that are only found when it's already too late.Interline

© 2022 - 2024 — McMap. All rights reserved.