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

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

So running this command gives the desire result in the output file:

$_vim_cmd = '/usr/local/bin/vim'; `$_vim_cmd . ' -c "normal \r" -c wq ~/vimwiki/testing.md'`;

This, however, does not:

$_vim_cmd = '/usr/local/bin/vim'; my $value = ' -c "normal \r" -c wq ~/vimwiki/testing.md'; `$_vim_cmd $value`;

The \r is not getting processed. I tried escaping it: \\r and I tried \Q and I tried qx() but nothing does the trick.

This also works but is far from ideal, obviously:

my $c1 = ' -c "normal '; my $c3 = '" -c wq ~/vimwiki/testing.md'; `$_vim_cmd $c1\r$c3`;

$PM = "Perl Monk's";
$MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
$nysus = $PM . ' ' . $MCF;
Click here if you love Perl Monks

Replies are listed 'Best First'.
Re: Running command with backslash not working as expected
by haukex (Archbishop) on May 09, 2019 at 08:08 UTC

    Backticks interpolate backslash escapes just like double quotes (the only exception being qx'...' ), while single quotes do not (Quote and Quote like Operators).

    `$_vim_cmd  . ' -c "normal \r" -c wq ~/vimwiki/testing.md'`;

    What's being passed to the external command* is a literal carriage return byte (despite all the extra quotes around it - Perl doesn't care about quotes within quotes).

    my $value = ' -c "normal \r" -c wq ~/vimwiki/testing.md'; `$_vim_cmd  $value`;

    Here, you're setting up a Perl string to contain the two-byte string \ plus r, and passing that to backticks, so that two-byte string then becomes part of the argument list*.

    (* Update: To clarify, what I mean is that this is Perl's interpretation of the string - what the external command actually receives in its argv after the string is optionally passed through the shell is a different matter, as the following explains.)

    use warnings; use strict; use Data::Dump; my $perl = "$^X -wMstrict -MData::Dump -e 'dd\\\@ARGV' -- "; dd qq{'"normal \r"'}; # prints "'\"normal \r\"'" print `$perl '"normal \r"'`; # => @ARGV is ("\"normal \r\"") my $value = '"normal \r"'; dd qq{$value}; # prints "\"normal \\r\"" print `$perl $value`; # => @ARGV is ("normal \\r") # "strace -s256 -fe trace=execve" (abbreviated by me) shows: # execve("/bin/sh", ["sh", "-c", "perl ... -- '\"normal \r\"'"], ... # execve("/bin/sh", ["sh", "-c", "perl ... -- \"normal \\r\""], ...

    Since you don't say what your desired result is, it's unclear to me what you mean by "works" and "doesn't work", but IMO this is another great reason to avoid the shell - as you can see above, Perl is calling the shell, and since it's unclear what this "default shell" is, this code is not going to be portable. It'd be better if you use something like capturex from IPC::System::Simple and then use Perl's normal quoting mechanisms to set up the exact strings that you want to get passed to the external process.

    use IPC::System::Simple qw/capturex/; print capturex($^X, qw/ -wMstrict -MData::Dump -e dd@ARGV -- /, "\r", "\\r"); # => prints ("\r", "\\r")

    Plus, all the other advice from my node here applies as well, e.g. you should check $? for errors. Also, using backticks in void context doesn't make much sense, maybe you want system instead? That supports avoiding the shell without having to pull in an extra module.

      Thanks. system results in a lot of unwanted ugliness to the screen. Besides, system returns a response code which I'm just going to throw away. Plus backticks are faster to type and less cluttering.

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

        system results in a lot of unwanted ugliness to the screen.

        There are ways to work around that. Many commands support a --quiet switch, which is my preferred method because I usually want to see external command's output to be able to inspect it for problems, or you could use another module to capture the command's STDOUT to suppress it.

        system returns a response code which I'm just going to throw away.

        I don't understand this argument - you're throwing away the backtick's return value as well... Plus, why would you call a command and then ignore whether it was successful or not?

        backticks are faster to type and less cluttering.

        Backticks don't offer a way to avoid the shell.

Re: Running command with backslash not working as expected
by jwkrahn (Abbot) on May 09, 2019 at 08:08 UTC

    In your first example the \r is enclosed in back-quotes which interpolate the same as a double quoted string, so \r gets translated to the carriage return character.

    In the second example the \r is enclosed in single quotes which do not interpolate so it contains the two characters \ and r.

    You need to enclose the string in double quotes:

    my $value = qq[ -c "normal \r" -c wq ~/vimwiki/testing.md];

      I originally had qq() (forgot to mention it) and it didn't work. It works now. There must have been another bug masking it. I was up way too late last night. Thanks.

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

      Ah, dammit. In some code that is not shown, I introduce the `\r' in singe quotes. Myopia.

      $PM = "Perl Monk's";
      $MCF = "Most Clueless Friar Abbot Bishop Pontiff Deacon Curate Priest Vicar";
      $nysus = $PM . ' ' . $MCF;
      Click here if you love Perl Monks

Re: Running command with backslash not working as expected
by hippo (Bishop) on May 09, 2019 at 08:25 UTC
    So running this command gives the desire result

    You don't say what the desired result is. Why not just use qq// until you have solved your problem whatever it might be. eg:

    use strict; use warnings; use Test::More tests => 1; my $args = qq#' -c "normal \r"'#; my $cmd = '/usr/local/bin/vim'; my $want = qq#/usr/local/bin/vim . ' -c "normal \r"'#; is ("$cmd . $args", $want); diag ("Tried for >$want<");

    Modify as necessary until you can construct the actual string you want. qx// uses the same interpolation as qq// so use the latter when testing.

    PS. Why are you using backticks when you are discarding the returned value anyway?

Re: Running command with backslash not working as expected
by Anonymous Monk on May 09, 2019 at 08:10 UTC

    Escape sequences like \t do not work in single quotes. Use qq{...} if you want to have double quotes inside double quote-equivalent operation. See Quote and Quote like Operators for more information.