Beefy Boxes and Bandwidth Generously Provided by pair Networks
Problems? Is your data what you think it is?
 
PerlMonks  

quoting/escaping file names

by famatte (Novice)
on Sep 08, 2014 at 19:22 UTC ( [id://1099889]=perlquestion: print w/replies, xml ) Need Help??

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

Dear Monks

I'm having a issue.
I'm using Linux and need to list all files from a Windows NTFS filesystem and perform some "actions".
my input will come linux "find" command or from " my $filename = $File::Find::name;" and I would like to "process" the file like ... $exec = `dosomething '$filename'`;

I found files like "$vaa it's not.txt" (here double quoted).

The issue is :
If I use double quotes, Perl will try to assign a value to $vaa .
If I use single quote, Perl tells me that there's a something wrong.

I already tried a lot of substitutions, with $filename =~ s/\'/\\\'/g; but nothing worked for a name like that.

Remember, in the same line (filename) $, space and single quote.

Can you help me ?

Replies are listed 'Best First'.
Re: quoting/escaping file names
by AppleFritter (Vicar) on Sep 08, 2014 at 20:41 UTC

    The shell's most likely your problem here, not Perl. Perl won't look at the contents of $filename if you interpolate it into a string, but the resulting string will be seen by a shell which will happily interpret $var if you're not careful.

    The following works for me:

    #!/usr/bin/perl use strict; use warnings; my $filename = q{$vaa it's not.txt}; $file =~ s/\$/\\\$/; my $exec = `touch "$filename"`;

    The double quotes (in the `` string) are passed to the shell there, and ensure that the spaces in $filename won't be an issue. The regular expression adds a backslash in front of the dollar sign; this keeps it from being interpreted by the shell. Finally, the single quote in the filename is not considered special by either Perl or the shell.

      It sounds like "$vaa it's not.txt" was only one example of a problematic filename, and so I gotta say I don't think your solution is general enough.

      Instead, I can only recommend to avoid the shell entirely (and only using the shell when you really know you really want its features - it really is another layer between Perl and the program you want to call, and you have to know how to use the shell, which isn't a given). This is possible via system (if you read its and the exec docs entirely because the invocation is a little tricky; and the output can be captured by something like Capture::Tiny), IPC::System::Simple, IPC::Run3, and a few others.

      Hello
      Here's a pratical example
      while(<>) { my $filename = q{$_}; $filename =~ s/\$/\\\$/; my $exec = `md5sum "$filename"`; print $exec ."\n"; }

      My input
      /mnt1/$Recycle.Bin/S-1-5-21-3093161954-3233498542-1968413288-3198/$I7Q +M78L.pdf<br> /mnt1/$Recycle.Bin/S-1-5-21-3093161954-3233498542-1968413288-3198/$ID8 +4PBU.pdf<br> /mnt1/$Recycle.Bin/S-1-5-21-3093161954-3233498542-1968413288-3198/$IDL +L4FU.JPG<br> /mnt1/$Recycle.Bin/S-1-5-21-3093161954-3233498542-1968413288-3198/$IE2 +ZPET.xls<br> /mnt1/$Recycle.Bin/S-1-5-21-3093161954-3233498542-1968413288-3198/$IF7 +3ZR5.jpg<br>

       find /mnt1/\$Recycle.Bin/ -type f | hash.pl The result ..
      md5sum: $_: No such file or directory <br> md5sum: $_: No such file or directory <br> md5sum: $_: No such file or directory <br> md5sum: $_: No such file or directory <br>

      But As I said before. file names could have spaces and quotes..

      ps: I know there's a DIGEST::MD5. Its only an example

        Try removing this line:

        my $filename = q{$_};

        q// (or q{} etc.) is just the single-quote operator; I used that instead of the more customary single quotes to avoid having to escape the single quote in the string itself (which I felt would only serve to complicate the issue).

        See Quote and Quote like Operators and Quote Like Operators in perlop. Since this IS a single-quote, BTW, q{$_} (which is exactly the same as '$_') does not interpolate $_ into the string, and you end up passing a literal $_ to md5sum. And of course, it's unlikely that a file with that name exists.

        Long story short, just remove that line, it was just there to provide an example filename to work with.

        Why did you type  my $filename = q{$_}; what do you think that does?
Re: quoting/escaping file names
by Corion (Patriarch) on Sep 08, 2014 at 19:41 UTC

    I'm not exactly sure where your problem lies. Can you show us a short example program that demonstrate the problem you encounter, and also where you encounter it?

    Maybe the part where you tried the substitutions and how they failed, and/or what you expected to happen?

      hello. Take a look at this ..
      while(<>) { chomp; my $filename = $_; $filename =~ s/\$/\\\$/g; $filename =~ s/ /\ /g; $filename =~ s/\'/\\\'/g; print $filename ."\n"; my $exec = `md5sum $filename`; print $exec ."\n"; }
      find /tmp/ -type f | ./hash.pl <br> /tmp/File Wasn\'t found.txt <br> md5sum: /tmp/File: No such file or directory<br> md5sum: Wasn't: No such file or directory<br> md5sum: found.txt: No such file or directory<br> <br>
      Now if I quote $filename
      while(<>) { chomp; my $filename = $_; $filename =~ s/\$/\\\$/g; $filename =~ s/ /\ /g; $filename =~ s/\'/\\\'/g; print $filename ."\n"; my $exec = `md5sum '$filename'`; print $exec ."\n"; }
      find /tmp/ -type f | ./hash.pl /tmp/File Wasn\'t found.txt sh: Syntax error: Unterminated quoted string
      Any Ideias ? Thanks

        You want to quote the string properly for your shell. Most likely String::ShellQuote will do what you want.

        Avoid the shell.

        use warnings; use strict; use IPC::System::Simple 'capturex'; while (my $fn=<DATA>) { chomp($fn); $fn=~s/\\n/\n/g; # test filenames with newlines print "Filename: <<<$fn>>>\n"; # Method 1: capturex my $m1 = capturex('echo','-n',$fn); print "capturex: ", $m1 eq $fn ? "PASS" : "FAIL <<<$fn>>>", "\n"; # Method 2: piped open open my $pipe, '-|', 'echo', '-n', $fn or die $!; my $m2 = do { local $/; <$pipe> }; close $pipe or die $! ? $! : "\$?=$?"; print "open: ", $m2 eq $fn ? "PASS" : "FAIL <<<$fn>>>", "\n"; } __DATA__ $vaa it's not.txt /mnt1/$Recycle.Bin/S-1-5-21-3093161954-3198/$I7QM78L.pdf /tmp/File Wasn't found.txt ! @ # $ % ^ & * ( - + = { ] | \ : ; " ' < , . / ? ~ ` filename\nwith\newlines

        All PASS on my system (Linux).

        Avoid the shell and you avoid the need for shell quoteing

        #!/usr/bin/perl -- ## ## ## perltidy -olq -csc -csci=3 -cscl="sub : BEGIN END " -otr -opr -ce +-nibc -i=4 -pt=0 "-nsak=*" ## perltidy -olq -csc -csci=10 -cscl="sub : BEGIN END if " -otr -opr +-ce -nibc -i=4 -pt=0 "-nsak=*" ## perltidy -olq -csc -csci=10 -cscl="sub : BEGIN END if while " -otr + -opr -ce -nibc -i=4 -pt=0 "-nsak=*" #!/usr/bin/perl -- use strict; use warnings; use Digest; use Digest::MD5; use Path::Tiny qw/ path tempfile tempdir cwd /; use File::Find::Rule qw/ find rule /; Main( @ARGV ); exit( 0 ); sub Main { ## self test #~ warn mmd5external( __FILE__ ); #~ warn mmd5perl( __FILE__ ); #~ warn mmd5( __FILE__ ); #~ rule( 'file' )->exec( \&Famatte )->in( '/tmp' ); #~ find( 'file' => 'exec' => \&Famatte , in => [ '/tmp' ] ); rule( 'file' => 'exec' => \&Famatte, )->in( '/tmp' ); } ## end sub Main sub Famatte { my( $shortname, $path, $fullname ) = @_; RealFamatte( $fullname ); return !!0; # discard filename } ## end sub Famatte sub RealFamatte { my( $filename ) = @_; print mmd5( $filename ), "\t$filename\n"; } ## end sub RealFamatte sub mmd5 { goto &mmd5perl; #~ goto &mmd5external; } ## end sub mmd5 #~ sub mmd5external { #~ use File::Which qw/ which /; #~ use Capture::Tiny qw/ capture /; #~ my( @args ) = @_; #~ my $cmd = which('md5sum'); #~ my( $stdout, $stderr, $exit ) = capture { #~ system( $cmd, @args ); #~ }; #~ if( $exit ){ #~ warn "uhoh got error exit( $exit ) : $stderr "; #~ } else { #~ $stdout =~ s/\s+$//; ## "chomp" #~ return $stdout; #~ } #~ } sub mmd5perl { my $fh = path( shift )->openr_raw; my $ctx = Digest->new( 'MD5' ); $ctx->addfile( $fh ); return $ctx->hexdigest; } ## end sub mmd5perl

        perltidy, Digest, Digest::MD5, File::Find::Rule, Path::Tiny, File::Which, Capture::Tiny.

        Path::Tiny, find/rule

Re: quoting/escaping file names
by Anonymous Monk on Sep 08, 2014 at 21:27 UTC

    The "traditional" *NIX way of doing things like this - using backticks to call the shell and putting together filenames "$like/$this" - starts showing its problems when you start going cross-platform and filenames get more interesting than /var/something/foo_bar (in my experience, "interesting" filenames are more common in Windows than *NIX).

    Instead, use something like Path::Class for your filename manipulations, and something like IPC::System::Simple for calling external programs (there are plenty of other similar modules but these should be enough to get started with). In this case, you should be able to do what you want with IPC::System::Simple's capturex('dosomething',$File::Find::name) as a replacement for the backticks. capturex avoids the shell and its interpolations altogether, so funky filenames should work.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1099889]
Approved by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others having an uproarious good time at the Monastery: (4)
As of 2024-04-16 19:33 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found