Perl escaping argument for bash execution

前端 未结 5 1474
感动是毒
感动是毒 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:29

    It's much easier to use single quotes in bash; then the only character you need to worry about is a single quote itself.

    ($arbitrary_string) =~ s/'/'"'"'/g;
    
    `echo '$arbitrary_string'`
    
    0 讨论(0)
  • 2020-12-21 11:30

    Don't use backticks to keep your string away from the shell, and hence all the quoting issues:

    system 'echo', $var;
    

    You write:

    I need stdout from this command unfortunately. Other thing is that variables are used in a lil bit complicated command which uses many pipes and stuff: echo $var | sed | grep | awk | and so on... and so on...

    You might want to investigate with something like this (untested, largely cribbed from the IPC::Open3 perldoc)

    use IPC::Open3;
    use Symbol 'gensym';
    
    my ($wtr, $rdr, $err);
    $err = gensym;
    my $pid = open3($wtr, $rdr, $err, 'sed|grep|awk|and so on');
    print $wtr $var;
    close $wtr;
    my $output = <$rdr>;
    waitpid $pid, 0;
    

    If your pipeline is very messy, store it in a shell script file, and invoke that script from perl.

    0 讨论(0)
  • 2020-12-21 11:33

    Use quotemeta:

    my $arg1 = quotemeta('string to be escaped');
    `echo $arg1`
    

    Or \Q\E (which is exactly what quotemeta is);

    my $arg1 = 'string to be escaped';
    `echo \Q$arg1\E`
    

    And also please note that using echo is a bad way to print arbitrary strings.

    And do NOT place any quotes around parameters if you're using quotemeta.

    0 讨论(0)
  • 2020-12-21 11:36

    CPAN to the rescue.

    String::ShellQuote should do what you need, although I concur with @glennjackman that system (or Capture::Tiny) is a better approach:

    use String::ShellQuote 'shell_quote';
    
    my $cmd = shell_quote( 'echo', 'This is sample text ending in a slash \\' );
    
    `$cmd`;
    
    0 讨论(0)
  • 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 '
    
    0 讨论(0)
提交回复
热议问题