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

On Windows, the following C program:

#include <stdio.h> int main(int argc, char* argv[]) { int i; for (i = 0; i < argc; ++i) { printf("%d:%s:\n", i, argv[i]); } return 0; }
when run like this:
> arg.exe "abc "" xyz"
prints:
0:arg.exe: 1:abc " xyz:
Though escaping double quotes inside a double quoted string by repeating them (as above) is ungainly, it is common in the Windows world and I need to support it.

Notice that the following Perl program:

for my $arg (@ARGV) { print "$arg:\n" }
when run with the same command line arguments:
> perl arg.pl "abc "" xyz"
prints instead:
abc ": xyz:

I tried:

use strict; use warnings; use Win32::API; my $getcmdline = Win32::API->new( 'kernel32.dll', 'GetCommandLine', [] +, 'P' ) or die "error: Win32::API GetCommandLine: $^E"; my $cmdline = pack 'Z*', $getcmdline->Call(); $cmdline =~ tr/\0//d; # remove any NULLs left over from pack Z* $cmdline =~ s/\s+$//; # remove trailing white space print "cmdline=$cmdline:\n";
to get at the Windows command line, but ran into the "random crashing problem" described at Win32::API Memory Exception with GetCommandLine() (which returns a static string).

It seems I'll need to use Win32::CommandLine (which I cannot currently get to build cleanly) or write a C front end to do the argument passing before launching Perl. Is there another way around this that I've missed?

Replies are listed 'Best First'.
Re: Parsing Windows CommandLine from Perl
by NetWallah (Canon) on Apr 17, 2015 at 19:11 UTC
    Parameter quoting rules for the Windows command line are discussed at
    http://stackoverflow.com/questions/7760545/cmd-escape-double-quotes-in-parameter

    The Relevant one for this issue is:

    Any double-quote directly following a closing quote is treated as (or as part of) plain unwrapped text that is adjacent to the double-quoted group, but only one double-quote:
    "Tim says, ""Hi!""" will act as one parameter: Tim says, "Hi!"
    You seem to want to send the 2 middle double-quotes as a part of one single parameter.
    To do that, you need to escape (For the Windows cmd interpreter), the 2 middle quotes thus:
    perl arg.pl "abc """""" xyz"
    I cannot claim to understand how c manages to get access to, and interpret the parameters without the cmd interpreter's intervention.

    Update:This link goes into depth to explain how WIndows command lines are processed. It answered my doubts on why the C code behaved differently:

    The C/C++ compiler which compiles the program secretly adds extra code to the executable that retrieves and parses the command line to extract the parameters before calling WinMain (or main). Thus, for a C/C++ executable on Windows, the parameter parsing rules are determined by the C/C++ compiler that compiled the program.
    and has this little gem:
    The missing undocumented rule has to do with how doubledouble quotes ("") are handled:

    Prior to 2008:
          If a closing " is followed immediately by another ", the 2nd " is accepted literally and added to the parameter.
    After 2008
          A double quote encountered outside a double quoted block starts a double quoted block.
          A double quote encountered inside a double quoted block:
                * not followed by another double quote ends the double quoted block.
                * followed immediately by another double quote (e.g. ""), a single double quote is added to the output, and the double quoted block continues.

    Anyway - I prefer fishmonger's answer below, which reflects the rule:
    Use \" to insert a literal "

            "You're only given one little spark of madness. You mustn't lose it."         - Robin Williams

Re: Parsing Windows CommandLine from Perl
by fishmonger (Chaplain) on Apr 17, 2015 at 15:05 UTC

    Perl uses the \ backslash character for escaping. Why not use it?

    #!/usr/bin/perl use warnings; use strict; for my $arg (@ARGV) { print "$arg\n" }

    arg.pl "abc \" xyz"

    abc " xyz

      Because the data may be piped in from something that generates what it knows Windows wants without knowing about Perl.

      Because the intended users might be non-programmers who have only just got to grips with the command line and the rules for doubling quotes, the latter perhaps from recording Excel macros.

      Because the test was written by a third party & that's what the coding shop is being paid to do.

      I'm sure that I could think of lots more examples given time, but what does it matter? It's the question we've been asked. "Just solve a different problem" isn't an answer I've often found helpful. I think it's unlikely to be an XY problem given the OP's history here.

      Regards,

      John Davies

Re: Parsing Windows CommandLine from Perl
by FreeBeerReekingMonk (Deacon) on Apr 17, 2015 at 20:07 UTC

    Fired up ye olde VM, and found the solution. You see, you can write a batch script that does the parsing for you... it looks like this:

    @ECHO OFF SetLocal EnableDelayedExpansion set ARGS= :parse_args if not %1.==. ( set ARG=%1 set ARGS=!ARGS! ^"!ARG:"=\"!^" shift goto :parse_args ) call perl getargs.pl %ARGS%

    I rewrote the perscript to fit the output of your C code, but it is basically the same:

    unshift(@ARGV,$0); for my $n (0..$#ARGV) { print "$n:$ARGV[$n]:\n" }

    The resulting output is then:

    C:\>parseargs.bat "abc "" def" bob 0:getargs.pl: 1:"abc "" def": 2:bob:

    Which is not exactly the same what you wanted, but it is actually exactly what you typed in as arguments. Now, it is time to s/""/"/g and you are ready to go. What I mean is:

    unshift(@ARGV,$0); @ARGV = map {s/^"(.*)"$/$1/ ? s/""/"/g && $_ : $_ } @ARGV; for my $n (0..$#ARGV) { print "$n:$ARGV[$n]:\n" }

    Dont ask why I used map... I am not sober at the moment...

    0:getargs.pl: 1:abc " def: 2:bob:

    note to self: do not forget bob

      That's a long way around the bush just to avoid using a backslash in the command.

      c:\dev>type arg.pl #!/usr/bin/perl use 5.010; use warnings; use strict; my $i = 0; say $i++ . ":$_" for ($0, @ARGV);

      The resulting output:

      c:\dev>perl arg.pl "abc \" xyz" bob 0:arg.pl 1:abc " xyz 2:bob
Re: Parsing Windows CommandLine from Perl
by Anonymous Monk on Apr 17, 2015 at 19:07 UTC
Re: Parsing Windows CommandLine from Perl
by crusty_collins (Friar) on Apr 17, 2015 at 16:13 UTC
    You can do it that way but a really good way is to use Getopt::Long

    untested

    # my program # test.pl use strict; use Getopt::Long; my $Dir; GetOptions ( "Dir=s" => \$Dir, );
    test.pl --Dir c:/path

      Using Getopt::Long is a better approach to passing args, but that doesn't answer the escaping question the OP is asking abut.

Re: Parsing Windows CommandLine from Perl
by kroach (Pilgrim) on Apr 17, 2015 at 17:23 UTC
    This seems to depend on the operating system. I tried running both the C version and the Perl version and both got:
    abc xyz
    as the only argument. It seems the Windows-specific modules are the way to go here.