osiv osiv - 2 months ago 7x
Bash Question

How Windows cmd transforms the command line when running Perl one-liners?

I know that Windows shell passes the whole line after an executable name to the executable, and it is task of the executable to parse it.
For instance,

C:\Users\osiv\Desktop\>perl -e "use File::Spec; print $_.' ' foreach (File::Spec->splitdir(\"C:\\Users\\osiv\\\"));"

should pass all characters after 'perl' to perl.exe found in a %PATH% value directory.

Explain me the output

C:\Users\osiv\Desktop\>perl -e "use File::Spec; print $_.' ' foreach (File::Spec->splitdir(\"C:\\Users\\osiv\\\"));"
Can't find string terminator '"' anywhere before EOF at -e line 1.

C:\Users\osiv\Desktop\>perl -e "use File::Spec; print $_.' ' foreach (File::Spec->splitdir(\"C:\\Users\\osiv\\\\"));"
Can't find string terminator '"' anywhere before EOF at -e line 1.

C:\Users\osiv\Desktop\>perl -e "use File::Spec; print $_.' ' foreach (File::Spec->splitdir(\"C:\\Users\\osiv\\\\\"));"
C: Users osiv

I expected that Perl parses the string passed by Windows shell by looking for strings which should have " at the start and the end. I escape them by \", and expected e.g.
to be parsed as
. But,
is not parsed as
, so how it is actually parsed?

Explanation of cmd.exe, CreateProcess command line string metacharacters


Who cares? The exact rules are convoluted and hard to remember. Just avoid the problem by not using double quotes in your Perl one liners. You know you have ', q{} and qq{} available.

All of cmd’s transformations are triggered by the presence of one of the metacharacters (, ), %, !, ^, ", <, >, &, and |. " is particularly interesting: when cmd is transforming a command line and sees a ", it copies a " to the new command line, then begins copying characters from the old command line to the new one without seeing whether any of these characters is a metacharacter. This copying continues until cmd either reaches the end of the command line, runs into a variable substitution, or sees another ". In the last case, cmd copies a " to the new command line and resumes normal processing. This behavior is almost, but not quite like what CommandLineFromArgvW does with the same character; the difference is that cmd does not know about the \" sequence and begins interpreting metacharacters earlier than we would expect.


C:\> perl -MFile::Spec::Functions=splitdir -MFile::HomeDir -we "print qq{'$_' } for splitdir home"

'C:' 'Users' 'sinan'

perl -wE "use File::Spec; print \"'$_' \" for File::Spec->splitdir( \"C:\\Users\\osiv\\\\\" )"

'C:' 'Users' 'osiv' ''

which shows that you should omit the trailing directory separator.

A better method of quoting

While the " metacharacter cannot fully protect metacharacters in our command lines against unintended shell interpretation, the ^ metacharacter can. When cmd transforms a command line and sees a ^, it ignores the ^ character itself and copies the next character to the new command line literally, metacharacter or not. That’s why ^ works as the line continuation character: it tells cmd to copy a subsequent newline as itself instead of regarding that newline as a command terminator. If we prefix with ^ every metacharacter in an argument string, cmd will transform that string into the one we mean to use.

Trying to follow that, the best I can come up with is:

perl -wE ^"use File::Spec; print \^"'$_' \^" for File::Spec-^>splitdir^(\^"C:\\Users\\osiv\\\\\^"^) ^"

As I said, avoid " in one-liners, make use of ', q{}, and qq{}.