Escaping characters like \", <, >, >>, or | in

2019-02-25 16:00发布

问题:

Trying to do:

fake-command.bat "ping -n 4 -w 1 127.0.0.1 >NUL"

and

fake-command.bat ping -n 4 -w 1 127.0.0.1

The batch file could look like:

@echo %*

It should return:

ping -n 4 -w 1 127.0.0.1 >NUL

and

ping -n 4 -w 1 127.0.0.1

Here a workaround:

@echo off
goto start
------------------------------------------------------

Usage : mystring <command>

  Quotes around the command are required only when the
  command involves redirection via <, >, >>, or |, etc.
  Quotes ensure that the redirection is applied to the 
  command, rather than the bat command itself.

Examples :

  mystring ping -n 4 -w 1 127.0.0.1
  mystring "ping -n 4 -w 1 127.0.0.1 >NUL"

------------------------------------------------------
:start
SETLOCAL ENABLEDELAYEDEXPANSION

SET "MYSTRING=%*"
ECHO My String = !MYSTRING!
SET !MYSTRING=MYSTRING:>=^>!

CALL :BATCH_FUNCTION !MYSTRING!
GOTO :EOF

:BATCH_FUNCTION
SET "ARGS=%~1"
ECHO Arguments = !ARGS!
endlocal
GOTO :EOF

the problem is, after: mystring "ping -n 1 127.0.0.1 >NUL"

It returns:

My String =
Arguments =

and after: mystring ping -n 1 127.0.0.1

It returns:

My String = ping -n 1 127.0.0.1
Arguments = ping

UPDATE: I updated the question with the following code from Get list of passed arguments in Windows batch script (.bat)

@echo off
SETLOCAL DisableDelayedExpansion
SETLOCAL
if exist param.txt (del param.txt)
for %%a in ('%*') do (
    set "prompt="
    echo on
    for %%b in ('%*') do rem * #%~1#
    @echo off
) > param.txt
ENDLOCAL

for /F "delims=" %%L in (param.txt) do (
  set "param1=%%L"
)
SETLOCAL EnableDelayedExpansion
set "param1=!param1:*#=!"
set "param1=!param1:~0,-2!"
echo My string is = !param1!

With this code, I can get escape characters, but with arguments not in quotes output is broken

What works?

mystring "# % $ ` ' ( ) < << >> > >NUL && & || | { } \ / - + = , . : ; ^ " &REM OK
mystring "ping -n 4 -w 1 127.0.0.1 >NUL" &REM OK

It returns:

My string is = # % $ ` ' ( ) < << >> > >NUL && & || | { } \ / - + = , . : ; ^
My string is = ping -n 4 -w 1 127.0.0.1 >NUL 

What does not work?

mystring ping -n 4 -w 1 127.0.0.1 &REM NOK
mystring "*" &REM NOK

It returns:

My string is = ping
The system can not find the file param.txt.
My string is = *

I do not see how in this code add the trick from Mofi.

回答1:

The batch code working also for single argument "ping -n 4 -w 1 127.0.0.1 >NUL" is:

@echo off
goto start
------------------------------------------------------

Usage : mystring <command>

  Quotes around the command are required only when the
  command involves redirection via <, >, >>, or |, etc.
  Quotes ensure that the redirection is applied to the
  command, rather than the bat command itself.

Examples :

  mystring ping -n 4 -w 1 127.0.0.1
  mystring "ping -n 4 -w 1 127.0.0.1 >NUL"

------------------------------------------------------
:start
SETLOCAL ENABLEDELAYEDEXPANSION

if "%~2" == "" (SET "MYSTRING=%~1") else (SET "MYSTRING=%*")
ECHO My String = !MYSTRING!
SET "!MYSTRING=MYSTRING:>=^>!"

CALL :BATCH_FUNCTION "!MYSTRING!"
GOTO :EOF

:BATCH_FUNCTION
SET "ARGS=%~1"
ECHO Arguments = !ARGS!
endlocal
GOTO :EOF

The IF condition at beginning makes the important difference.

When the batch file is called with

fake-command.bat ping -n 4 -w 1 127.0.0.1

it is called with 6 arguments and therefore %* is the right method to assign all arguments to the variable MYSTRING.

But calling the batch file with

fake-command.bat "ping -n 4 -w 1 127.0.0.1 >NUL"

means just 1 argument enclosed in double quotes is passed to the batch file.

The line

SET "MYSTRING=%*"

resulted in assigning to variable MYSTRING the string

"ping -n 4 -w 1 127.0.0.1 >NUL"

with the quotes included in string. The remaining quotes causes the wrong result on further processing.

It is not clear what should be assigned to variable ARGS. If all arguments passed to batch file should be assigned to this variable, it is necessary to enclose !MYSTRING! also in double quotes on calling the subroutine to pass all arguments of the batch file as 1 argument to the subroutine.

The command FOR could be used to split up the string assigned to MYSTRING into command (ping) and its arguments (with redirector operators).


UPDATE (after question was updated):

It is not really clear what is the task at all. This batch code is designed to get just first parameter passed to the batch file which is the reason for #%1# in second, inner FOR loop. Exactly this is what the batch code does although it contains a small mistake with no effect on result.

set "prompt=" has no effect as it is impossible to change prompt text to nothing. So the file params.txt still contains the prompt text. Better is using prompt $_ or set "prompt=$_" which defines prompt text being only carriage return + line-feed and therefore producing just an empty line ignored by later used command FOR on reading in the produced file params.txt. The previous prompt definition is restored with endlocal after the first outer FOR loop.

Paul, you just replaced both (1) by ('%*') which has only the effect that for each parameter passed to the batch file the first and only the first parameter is written to text file params.txt and next read in several times. Just look on params.txt to see what it contains after running the batch file with the various parameters. This is of course not really useful.

Here is the batch code modified to output all parameters passed to the batch file.

@echo off
setlocal DisableDelayedExpansion
del param.txt 2>nul
for %%a in (1) do (
    set "prompt=$_"
    echo on
    for %%b in (%*) do rem * #%%~b#
    @echo off
) > param.txt
endlocal

setlocal EnableDelayedExpansion
set "ParaNumber=1"
set "AllParameters="
for /F "delims=" %%L in (param.txt) do (
    set "Parameter=%%L"
    set "Parameter=!Parameter:*#=!"
    set "Parameter=!Parameter:~0,-2!"
    set "AllParameters=!AllParameters! !Parameter!"
    echo Parameter !ParaNumber! = !Parameter!
    set /A ParaNumber+=1
)
set "AllParameters=!AllParameters:~1!"
echo All parameters: !AllParameters!
endlocal

Calling the batch file three times with the following three strings one after the other

"# % $ ` ' ( ) < << >> > >NUL && & || | { } \ / - + = , . : ; ^ "
"ping -n 4 -w 1 127.0.0.1 >NUL"
ping -n 4 -w 1 127.0.0.1

results in the following three outputs

Parameter 1 = # % $ ` ' ( ) < << >> > >NUL && & || | { } \ / - + = , . : ; ^
All parameters: # % $ ` ' ( ) < << >> > >NUL && & || | { } \ / - + = , . : ; ^

Parameter 1 = ping -n 4 -w 1 127.0.0.1 >NUL
All parameters: ping -n 4 -w 1 127.0.0.1 >NUL

Parameter 1 = ping
Parameter 2 = -n
Parameter 3 = 4
Parameter 4 = -w
Parameter 5 = 1
Parameter 6 = 127.0.0.1
All parameters: ping -n 4 -w 1 127.0.0.1 >NUL

Calling the batch file with "*" results now in getting listed as parameters 1 to n all non hidden files in current directory. So this modified version of the batch file does not work for parameters which are valid file name patterns.