Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

(Windows) verbatim command line arguments inside Perl script

by LanX (Saint)
on Apr 16, 2021 at 12:37 UTC ( [id://11131372]=perlquestion: print w/replies, xml ) Need Help??

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

Hi

I need to write a Perl script which is generating a .bat-file while passing it's command line arguments 1-to-1

Is there any way to access the "raw" command-line in order to catch quoting and escaping?

To highlight the problem:

D:\>perl -E"say qq(@ARGV)" "1 2" "3" 4 1 2 3 4

As you can see are all quotes and escapes lost while calling Perl (for good reasons)

But my script has to generate another .bat which does

... some_cmd "1 2" "3" 4 ...

Typical edge-case justifying this problem are win-paths with whitespace, but cmd.exe has also some arcane escaping rules.

I already have a workaround:

I discovered that .bat has %* to hold the verbatim command line arguments, and came up with a workaround of a .bat calling my perl-script

D:\tmp\exp>type cmd_wrapper.bat @echo off echo %* |perl -wE"chomp(my $args = <STDIN>);say qq(some_cmd $args)" D:\tmp\exp>cmd_wrapper.bat "1 2" "3" 4 some_cmd "1 2" "3" 4 D:\tmp\exp>

Question: any better solution?

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery

PS: I don't think this question is overly Win specific, when creating a bash script on Linux I'd face the same problem.

Replies are listed 'Best First'.
Re: (Windows) verbatim command line arguments inside Perl script (updated)
by haukex (Archbishop) on Apr 16, 2021 at 15:13 UTC
    Is there any way to access the "raw" command-line in order to catch quoting and escaping?

    The first google result for "windows api get command line" is GetCommandLineA. So I hunted on CPAN and thanks to Win32::API's samples I got:

    use warnings; use strict; use feature 'state'; use Win32::API; sub GetCommandLine { state $GetCommandLine = Win32::API->new("kernel32", "GetCommandLin +e", [], 'P'); ( my $c = pack("a1024", $GetCommandLine->Call()) ) =~ s/\0*$//; return $c; } use Data::Dumper; $Data::Dumper::Useqq=1; my $cmdline = GetCommandLine(); print Dumper($cmdline); $cmdline =~ s/^(?:\Q$^X\E|perl(?:\.exe|\.bat|\.cmd)?)\s+(?:\Q$0\E\s+)? +//; # hack!! print "some_cmd ", $cmdline, "\n"; __END__ X:\Perl>perl cmdline.pl "1 2" "3" 4 $VAR1 = "perl cmdline.pl \"1 2\" \"3\" 4"; some_cmd "1 2" "3" 4

    Update:

    PS: I don't think this question is overly Win specific, when creating a bash script on Linux I'd face the same problem.

    On Linux, your Perl script could theoretically have been invoked by execvp, meaning there actually is no command line in the first place. I think you'd have to settle with using String::ShellQuote on @ARGV (note that it only supports bash style quoting).

      Awesome, thanks! :)

      I'll try this out and will give feedback.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

Re: (Windows) verbatim command line arguments inside Perl script
by haj (Vicar) on Apr 16, 2021 at 14:31 UTC

    The quoting gets lost when the shell parses the command line arguments. Your @ARGV has three elements, as you can check with perl -E"$,=':';say @ARGV" "1 2" "3" 4, which gives 1 2:3:4.

    The echo command is exempt from this parsing: it is a builtin of cmd.exe.

    You can accept the list from @ARGV and re-establish a "correct" quoting by passing the list to the appropriate function of Win32::ShellQuote] (or ShellQuote::Any, if portability matters). The documentation of Win32::ShellQuote comes with a warning, though:

    Perl will try to detect if you need the shell by detecting shell metacharacters. The routine that checks that uses different quoting rules from both cmd.exe and the native Win32 parsing. Extra work must therefore be done to protect against this autodetection.
    Your mileage will vary :)
      The quoting gets lost when the shell parses the command line arguments.

      I'm not an expert, but I thought that part of the issue is that on Windows, that's actually not what happens? I'm looking at Re^3: Perl Rename.

      Update: Unless with that sentence you were referring to *NIX systems, in which case you're right of course.

        Ah, you're correct, of course. That was my initial thought (because that's how it is done in Linux), but then I read the docs of Win32::ShellQuote which tells a different story:

        Windows passes its arguments as a single string instead of an array as other platforms do.
        - and then I copied a presumably relevant part of that docs into my reply without correcting the first sentence.

        BTW: Windows has other ... interesting ... challenges when parsing command line arguments. Try this:

        perl -E "say @ARGV" €
      Thanks, I was hoping for such modules ... :)

      ... but

      > The quoting gets lost when the shell parses the command line arguments.

      AFAIK that's true on Linux but not on Win

      quoting Win32::ShellQuote

      > Windows passes its arguments as a single string instead of an array as other platforms do. In almost all cases, the standard Win32 CommandLineToArgvW function is used to parse this string

      So the win-built of Perl is deconstructing the input-line into @ARGV. (we had a similar discussion not long ago)

      Hence it's not unthinkable to have a special var akin to %* in Perl to access that original line.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Yeah, I noticed (too late) that I failed to correct that sentence after reading the docs of Win32::ShellQuote. Sorry.

        If I understand correctly, it is even more convoluted than that: cmd.exe does perform some substitutions, so there really is no way to get the exact command line, even though Windows provides a "command tail" instead of an argument vector. The Windows build of perl does nothing special here — the C runtime library startup code parses the command tail and builds an argument vector that perl then packages into @ARGV.

Re: (Windows) verbatim command line arguments inside Perl script
by afoken (Chancellor) on Apr 16, 2021 at 14:12 UTC
    I don't think this question is overly Win specific, when creating a bash script on Linux I'd face the same problem.

    Except that (1) bash has sane quoting rules and (2) you don't need to mess with quoting and bash at all because you can use the list form of system() and exec(), and so can pass each argument as intended (see The problem of "the" default shell).

    Question: any better solution?

    For generating a batch file with tons of insane quoting? Here-documents, with or without interpolation, depending on your needs.

    Alexander

    --
    Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
      > Except that (1) bash has sane quoting rules and (2) you don't need to mess with quoting and bash at all

      That's - like expected - a lot of emotion ...

      Bash is much saner but not sane.

      And since I'm biased towards bash - I know it far better - I can't even tell if I'm objective.

      > because you can use the list form of system() and exec(), and so can pass each argument as intended (see The problem of "the" default shell).

      You are missing my point, not sure if you even read the OP ...

      I have to create scripts in the "default shell" with Perl.

      And need the recreate the input 1-to-1.

      And at least my workaround works so far...

      > see The problem of "the" default shell).

      thanks

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

Re: (Windows) verbatim command line arguments inside Perl script
by ikegami (Patriarch) on Apr 19, 2021 at 21:17 UTC

    Is there any way to access the "raw" command-line in order to catch quoting and escaping?

    Wrong question. You are trying to build a shell command, so the question is how to convert strings into shell command arguments. For that, there's Win32::ShellQuote's quote_cmd.

    use Win32::ShellQuote qw( quote_cmd ); say quote_cmd("some_cmd", @ARGV);

    Seeking work! You can reach me at ikegami@adaelis.com

Re: (Windows) verbatim command line arguments inside Perl script
by BillKSmith (Monsignor) on Apr 16, 2021 at 15:19 UTC
    Not exactly what you want, but note that windows does allow you to escape quotes.
    >perl -E"say qq(@ARGV)" "\"1 2\" \"3\" 4" "1 2" "3" 4
    Bill
      cmd.exe is also allowing unclosed quotes, with is frankly a PITA

      D:\tmp\exp>perl -E"say join'|',@ARGV" "1\" 2" "3 1" 2|3

      And I already noticed that the escaped quotes are killing my workaround

      D:\tmp\exp>type cmd_wrapper.bat @echo off echo %*| perl.exe .\wrapped_pl.pl D:\tmp\exp>cmd_wrapper.bat "1\" 2" "1\" 2"| perl.exe .\wrapped_pl.pl

      what seems to happen is that

      1. %* is expanded like a macro before executing the bat
      2. The second quote is not considered escaped for echo
      3. The pipe is seen as part of an unclosed "string"
      4. hence the call to perl.exe is echo'ed like a string and never executed

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others surveying the Monastery: (3)
As of 2024-04-19 05:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found