http://www.perlmonks.org?node_id=1010031

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

Hi everyone!

I have to process a lot of filenames and directory names which may contain spaces: I want to change spaces with, lets say, _ symbol. I've googled for a solution; and I've found a rather ugly Bash string which however works fine.

I've decided to write a much more good-looking Perl script which would do the same thing. But the only code I was able to compose is the following code:

#!/usr/bin/perl use strict; use warnings; use File::Find; my $dir = "/home/ivan/M1"; find(\&wanted, $dir); sub wanted { my $file = $_; # storing unprocessed filename $_ =~ s/ /_/g; # replacing spaces with _ rename "$File::Find::dir/$file", "$File::Find::dir/$_"; # renaming + files }

I've run it; first time I've run it, it returned an error like this:

Can't cd to (/home/ivan/M1/) Some Dirname With Spaces: no such file or + directory at ./1.pl line 16

But it had processed all the filenames and changed spaces with _. I've run the script second time and it didn't return any mistake and all of my filenames and dirnames were processed.

My question is: what have I done wrong and how to improve this script? I'm a newbe in Perl and, to tell the truth, in Unix, so please don't blame me too hard :) . Thank you in advance.

Replies are listed 'Best First'.
Re: File::Find and replacing spaces in filenames.
by Kenosis (Priest) on Dec 22, 2012 at 17:04 UTC
    • What happens when "my_file.txt" already exists and you encounter "my file.txt" (same issue with dir names)?
    • Consider just returning if no spaces found (no renaming necessary).
    • Will this renaming break anything that depends on previously-existing paths?

      1. Well, that is a problem. I'm going to think about it tonight.

      2. like this return 1 if $_ !~ /\s+/; ?

      3. No, it won't; files to process are just photos which are just stored on hdd, no software refers to them.

      Thanks!

        2. like this return 1 if $_ !~ /\s+/; ?

        No, because then you would have to perform a second match, which is unneeded. The s// operator returns the number of substitutions made, and 0 is perl's value for false.

        if ($_ =~ s/ /_/g) { # rename } else { return 1 }

        However, according to the docs for File::Find the return value of the wanted() function is ignored, so just write:

        if ($_ =~ s/ /_/g) { # rename }
Re: File::Find and replacing spaces in filenames.
by Anonymous Monk on Dec 22, 2012 at 19:11 UTC

    Your logic ends up like this:

    1. Enter directory
    2. Call wanted() sub, which renames the directory
    3. Return from the sub
    4. Get confused, since the directory we're in no longer exists

    You can make it work correctly if you only do the renaming after you've finished with the directory's children. File::Find provides the bydepth option to do that. (Or alternatively, the finddepth() subroutine)

      Thank you a lot! I should have read the description of the module more carefuly.
        I should have read the description of the module more carefuly.

        Amen brother, amen!

Re: File::Find and replacing spaces in filenames.
by mimosinnet (Beadle) on May 17, 2017 at 21:55 UTC
    I have come across with the same issue, with individual files, and this is what I have got with Perl6:
    #!/usr/bin/env perl6 use v6; # Substitute special chars to underscore my token special_char { <[ \s \( \) ]> }; multi MAIN( Str $filename where ( $filename.IO ~~ :f and $filename.match( &special_char ) ) ) { $filename.IO.rename( $filename.subst( /<special_char>/ , '_', :g) +); } multi MAIN( Str $filename where ( $filename.IO ~~ :f and ! $filename.match( & +special_char) ) ) { say "File '$filename' has no spepcial characters"; } multi MAIN( Str $filename where !($filename.IO ~~ :f) ) { say "File '$filename' does not exist"; } sub USAGE() { say "Usage: NoSpace file-name"; }