5

How do I call a subroutine with a parameter being a variable containing an ampersand (&)?

There is no error, but the call never seems to execute.

example.bat

@echo off
setlocal enableDelayedExpansion

rem Doesn't work
set val=with^&ampersand
call :Output !val!

rem Works fine
set val=without_ampersand
call :Output !val!
goto End

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

End:

Output:

Called: without_ampersand

Edit:

The usage of delayedExpansion is not required. It was just used in this example. A way to do it without using delayedExpansion is preferred.

The question is focusing on "How do I call" rather than "How do I initially set the variable". The variable may come from user input or a for /f loop (as is my case).

  • & is a reserved character, which you need to escape stackoverflow.com/questions/1741546/… – spikey_richie Feb 6 '18 at 11:32
  • @spikey_richie In my example, I have escaped it using ^. But it won't pass the value to the subroutine. – ANisus Feb 6 '18 at 11:40
  • You are using delayed expansion, so I suspect you might need a lot more escaping... – user1686 Feb 6 '18 at 11:49
  • If you go back to basics and just do set val=test^&test at the command prompt, then just run set without a value does the list return val=test&test? – spikey_richie Feb 6 '18 at 11:51
  • Just as grawity said.... try set val=with^^^&ampersand so a lot more escaping. – Pimp Juice IT Feb 6 '18 at 11:53
2

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
  • Wow. Okay. That link was informative, and kind of scary, reading :) . But the option of passing by reference seems to be the real solution in this case. Thanks for the well written answer! – ANisus Feb 9 '18 at 9:56
0

If you add an additional ^ and quotation marks it will work:

@echo off
setlocal enableDelayedExpansion

rem Doesn't work
set "val=with^^&ampersand"
call :Output !val!

rem Works fine
set "val=without_ampersand"
call :Output !val!
goto End

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

SET LOCAL EnableDelayedExpansion requires 2 escape characters:

ECHO 123 ^^& 456^^! will output 123 & 456!.

You should also use SET always with quotation marks!

  • Since it is the calling (rather than the setting) that is in question, do you mean I must do a replace (set val=!val:^&=^^^^^&!) prior to the call? It doesn't seem to work. – ANisus Feb 6 '18 at 12:53
  • Nope - That cannot work because of CALL caret doubling – dbenham Feb 6 '18 at 15:58
  • @ANisus Oversaw that, sorry. But do you really need the & combined with call there? If so, you could edit the variable and replace the & with something else which you then replace again with a & later on. Or you simply use the word and (if that's the purpose of the & here). – FatalBulletHit Feb 7 '18 at 0:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.