views:

114

answers:

2

I have a Windows CMD script that accepts a number of parameters and executes an EXE, passing first some hard-coded arguments and then all of the parameters from the user. The CMD script looks like this:

launcher.exe paramX paramY %*

The user would execute the CMD script from the Windows shell as follows:

launcher.cmd param1 param2 param3 [...]

The problem I have is that if the parameters to the CMD script contain shell special characters like < > and ^, the user is forced to escape these by preceding each with 3 caret ^ shell escape characters.

Two Examples

1) To pass the argument ten>one to the EXE, the user must launch the CMD as follows:

launcher.cmd ten^^^>one

The reason for this is that the shell special characters ^ and > are interpreted by the command shell at two levels, first on the command line and second inside the CMD script. So, the shell escaping with the caret ^ shell escape character must be applied twice. The problem is that this is non-obvious to the user and looks ugly.

For this example, a nicer solution is to surround the argument with double quotes. However, this breaks down for more complex examples that include a literal double quote in the argument.

2) To pass the argument "^ to the EXE, the user must launch the CMD as follows:

launcher.cmd "\"^^^^"

In my case I want to support arguments that contain any sequence of low ASCII characters, excluding control characters, i.e. code points 0x20 to 0x7E. I understand that there will be examples where the user will have to escape certain shell special characters with a caret. However, I don't want the user to have to use 3 carets every time in these cases just because they happen to be calling a CMD script instead of an EXE.

I can solve this problem by replacing the CMD script with an EXE that does the same. However, is there any way to alter the CMD script so that it passes its parameters through to the EXE without interpreting the shell special characters?

A: 

Does this help:

EscapPipes.Cmd:

@echo off 

:Start
    If [%1]==[] goto :eof
    @Echo %1
    shift
goto :Start

When started thus:

EscapPipes.Cmd Andy Pandy "Pudding | > < and pie"  

gives

Andy
Pandy
"Pudding | > < and pie"

As soon as you strip the quotes the pipe symbols will become live.

Andy Morris
Surrounding the arguments with double quotes does of course work for simple examples, but breaks down with certain more complex ones, eventually forcing the user to escape key bits with carets, twice. I have added a second more complex example to the question to illustrate.
Paul Urban
Your script makes use of ECHO, which just prints its arguments without parsing them, whereas an EXE will usually parse the quotes and backslashes in order to provide an array of arguments to the application code. Nevertheless, you'll find that my second example breaks your script :-)
Paul Urban
A: 

One way is to work with delayed expansion inside of the batch, because then the special characters lose there "special" meanings.

The only problem is to get the parameters into a variable.

Something like could help

@echo off
setlocal DisableDelayedExpansion
rem ** At this point the delayedExpansion should be disabled
rem ** otherwise an exclamation mark in %1 can remove carets
set "param1=%~1"

setlocal EnableDelayedExpansion
rem ** Now you can use the param1, independent of the content, even with carets or quotes
rem ** but be careful with call's, because they start a second round of expansion
echo !param1!
set "tmp=!param1:~1,4!"

Now the parameters can be surround by quotation marks, so there the carets aren't neccessary anymore. Example launcher.bat "abc>def&geh%ijk|lmn^opq!"

The only remaining problematic special character seems to be the quotation mark.

[Edit/Improve] I create another way to retrieve a parameter, I assume it can accept any string also your second example.
Even really hard strings like

launcher "^
launcher ten^>one
launcher "&"^&

@echo off
setlocal DisableDelayedExpansion
set "prompt=X"
for %%a in (1 ) do (
    @echo on
    for %%b in (4) do (
        rem #%1#
    ) 
) > XY.txt
@echo off
for /F "delims=" %%a in (xy.txt) DO (
  set "param=%%a"
)
setlocal EnableDelayedExpansion
set param=!param:~7,-4!
echo param='!param!'

How it works?
The only way I have found to expand %1 without expanding the special characters like " or ^ is in a REM statement (For REM that's not completly true, but that is an other story) Ok, the only problem is that a REM is a remark and has no effect :-)

But if you use echo on also rem lines are echoed before they are executed (execute for rem is a nice word).
The next problem is that it is displayed and you can not redirect this debug output with the normal > debug.txt. This is also true if you use a for-loop.

Ok, you can redirect the echo on output with a call like

echo on
call :myFunc > debug.txt

But if you call a function you can't access the %1 of the batch file anymore.

But with a double for-loop, it is possible to activate the redirection for the debug output and it's still possible to access %1.
I change the prompt to "X", so I know it is always only one character long.

The only thing left is to explain why I append a # to %1.
That's because, some special characters are recognized in some situations even in a REM line, obviously ;-)

rem This is a remark^
rem This_is_a_multiline^
rem "This is also a multiline"^

So the # suppress a possible multiline situation.

jeb
Adding 'setlocal EnableDelayedExpansion' to the top of the CMD script unfortunately does not resolve the issue. For the second example, where we want to pass the parameter literal "^, it's still necessary for the user to double-escape the caret as shown.
Paul Urban
Ok, a <LF> can break this code, but it seems that other characters are working
jeb
Fantastic effort! I'm testing it, but it only seems to echo the first argument I pass to it?
Paul Urban