How to pass parameter with ampersand when calling a batch from a batch

Viewed 786

foo.bat:

call qwe.bat "hello && bye"

qwe.bat:

@echo off
set cmd=%~1
echo "%cmd%"

When I execute foo.bat I get an error:

'bye' is not recognized as an internal or external command,
operable program or batch file.
"hello "

So how to pass the string "hello && bye" from foo.bat to qwe.bat? Looks like ampersands are causing the problem

2 Answers

The batch escape rules are quite nasty, but the behavior is totally predictable if you know the rules.

The info you need to understand the problem is available at How does the Windows Command Interpreter (CMD.EXE) parse scripts? in phases 1, 2, 5, and 6 of the accepted answer. But good luck absorbing that info any time soon :-)

There are two fundamental design issues that lead to your problem: - Phase 6 doubles all carets, which then restarts phase 2 (actually phases 1, 1.5 and 2). - But phase 2 requires & to be escaped as ^&. Note it must be a single ^, not doubled!

The only way to get your approach to work is to introduce the ^ after the phase 6 caret doubling has occurred.

@echo off
setlocal enableDelayedExpansion
set "ESC=^"

rem Calling with delayed expansion value
set "val=with%%ESC%%&ampersand"
call :Output !val!

rem Calling with a string literal
call :Output with%%ESC%%^&ampersand

exit /b

:Output
set "line=%1"
echo Called: !line!
goto :eof

ESC is defined to hold ^. The first round of phase 1 expands %%ESC%% to %ESC% the second round of phase 1 (initiated by phase 6) expands %ESC% to ^

This is all totally impractical, especially if you don't know what the content is going to be.

The only sensible strategy to reliably pass any value into a CALLed routine is to pass by reference. Pass the name of a variable that contains the string value, and expand that value within your subroutine using delayed expansion.

@echo off
setlocal enableDelayedExpansion
set "val=with&ampersand"
call :Output val
exit /b

:Output
set "line=!%~1!"
echo Called: !line!
exit /b

After defining your variable using the recommended syntax, Set "Var=String Value", you can simply use delayed expansion:

@Echo Off
Set "Cmnd=%~1"
SetLocal EnableDelayedExpansion
Echo !Cmnd!
Pause
EndLocal
Related