Perl escaping argument for bash execution

前端 未结 5 1554
感动是毒
感动是毒 2020-12-21 11:16

I\'ve written some code in Perl which executes some bash command within its execution. My problem was when bash command attributes contained white space inside which failed

5条回答
  •  难免孤独
    2020-12-21 11:42

    When calling directly from perl, as Glenn Jackman says, I would use system or exec to avoid quoting trouble and to capture the output, for example to compute the md5sum of a file:

    my $pid, $safe_kid; 
    die "cannot fork: $!" unless defined ($pid = open($safe_kid, "-|"));
    if ($pid == 0) {
       # This is the child process, exec md5sum
       exec('/usr/bin/md5sum', '--binary', $filename) or die "can't exec md5sum: $!";
    } else {
       # This is the parent process, read data (we do not need to wait for
       # child process termination)
       @sum = <$safe_kid>;
       close $safe_kid; # $? contains status 
    }
    if ($?!=0) {
       die "Problem computing hashsums on '$filename': $!\n";
    }
    

    On a related note, I was looking for a way to print command line arguments to the shell for the user to copy and paste. I hit upon the idea of single-quoting everything, and using echo to recompose the string if a single-quote was already present, although using String::ShellQuote seems to be a better idea, really:

    #!/usr/bin/perl
    
    use strict;
    
    testEscapeForBash("Hello World");
    testEscapeForBash("Hello World!");
    testEscapeForBash("Nothing_special_here.txt");
    testEscapeForBash("With'One Single Quote");
    testEscapeForBash("With 'Two Single' Quotes");
    testEscapeForBash("'With Surrounding Single Quotes'");
    testEscapeForBash("With \$ Dollar Sign");
    testEscapeForBash("With * Fileglob");
    testEscapeForBash("With ! History Expansion Sign");
    testEscapeForBash("   ");
    testEscapeForBash(" Some surrounding spaces ");
    
    sub testEscapeForBash {
       my ($in) = @_;
       my $out = escapeForBash($in);
       print "[$in] gives\n       $out\n";
    }
    
    sub escapeForBash {
       my ($name) = @_;
       if (!$name) {
          die "Empty name passed to 'escapeForBash'"
       }
       my @parts = split(/'/,$name,-1); # limit is negative to catch trailing quote
       my $res;
       if (@parts == 1) {
          $res = "'$name'"
       }
       elsif (@parts > 1) {
          $res = '$(echo ';
          my $first = 1;
          for my $part (@parts) {
             $res .= "\"'\"" unless $first;
             $first = 0;
             $res .= "'";
             $res .= $part;
             $res .= "'";
          }
          $res .= ')';
       }
       else {
          die "Weird number of parts: @parts"
       }
       return $res
    }
    

    Let's run this:

    [Hello World] gives
           'Hello World'
    [Hello World!] gives
           'Hello World!'
    [Nothing_special_here.txt] gives
           'Nothing_special_here.txt'
    [With'One Single Quote] gives
           $(echo 'With'"'"'One Single Quote')
    [With 'Two Single' Quotes] gives
           $(echo 'With '"'"'Two Single'"'"' Quotes')
    ['With Surrounding Single Quotes'] gives
           $(echo ''"'"'With Surrounding Single Quotes'"'"'')
    [With $ Dollar Sign] gives
           'With $ Dollar Sign'
    [With * Fileglob] gives
           'With * Fileglob'
    [With ! History Expansion Sign] gives
           'With ! History Expansion Sign'
    [   ] gives
           '   '
    [ Some surrounding spaces ] gives
           ' Some surrounding spaces '
    

提交回复
热议问题