1

In a code like this:

set /p "PROGNAME=enter the name of the prog: "
set /p "VERSION=enter the version of the prog: "
setlocal enabledelayedexpansion
if "%PROGNAME%"=="foo" (
    set "OPTIONS=(someParams)"
    set "PROGPATH=C:\MyPath\%PROGNAME%%OPTIONS%_%VERSION%.exe"
) else if "%PROGNAME%"=="bar" (
    set "OPTIONS=(otherParams)"
    set "PROGPATH=C:\MyPath\%PROGNAME%%OPTIONS%_%VERSION%.exe"
)
set "PROGDLL=!PROGPATH:%PROGNAME%=%PROGNAME%dll!"
endlocal

I have learned that I need enabledelayedexpansion both for the PROGPATH variable, because it's set in the same loop than the OPTIONS variable it needs, and for the PROGDLL variable, because of the substitution.

However, in this particular case, I do not want the variables to be local. Like, not at all; I wanna access them even after the end of the script. So enabledelayedexpansion can't be used.

I then made something like this:

set /p "PROGNAME=enter the name of the prog: "
set /p "VERSION=enter the version of the prog: "
if "%PROGNAME%"=="foo" (
    set "OPTIONS=(someParams)"
    call set "PROGPATH=C:\MyPath\%PROGNAME%%OPTIONS%_%VERSION%.exe"
    call set "PROGDLL=%%PROGPATH:foo=foodll%%"
) else if "%PROGNAME%"=="bar" (
    set "OPTIONS=(otherParams)"
    call set "PROGPATH=C:\MyPath\%PROGNAME%%%OPTIONS%%_%VERSION%.exe"
    call set "PROGDLL=%%PROGPATH:bar=bardll%%"
)

And while this works perfectly, I am now quite lost: if all it needed was a call set, what's the point of enabledelayedexpansion ?? I mean, I've been using enabledelayedexpansion in my script each time I needed to set some dependent variables in a loop, should I replace all of those by call set and delete the enabledelayedexpansion ?

I would like to understand when I should use enabledelayedexpansion and when I should use call set, in general.

(and also, why the double %% with call set ?)

Ablia
  • 143
  • 9
  • In your example I see no reason to be using the intermediary variable option. The reason for the %% expansion with call is described [here](https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts?r=SearchResults). Call is somewhat resource intensive and has the effect of doubling any caret `^` in parameters that follow it. Delayed expansion will result in unescaped `!` expanding into empty variables if paired in an und3fined string, or if singular being removed. Which one you use depends on whether either of these factors presents a problem. – T3RR0R Sep 30 '20 at 10:28
  • Note also that variable values can be 'tunneled' past an endlocal using `(endlocal & set "var=value") – T3RR0R Sep 30 '20 at 10:30
  • Delayed expansion is most valuable where batch macros are concerned, as variables can be assigned to a macro before they are defined, with delayed expansion enabled prior to the macros use. See [here](https://pastebin.com/n9xTqwtk) for an example script that leverages macros and delayed expansion as an alternative to functions or repetitive code blocks. – T3RR0R Sep 30 '20 at 10:38
  • Can you please clarify what you mean by this. _'in this particular case, I do not want the variables to be local. Like, not at all; I wanna access them even after the end of the script.'_? I ask because using `set` does not create persistent variables, they will only remain defined withginn the context of the cmd.exe instance running the script, once that instance ends those variables will no longer be defined. – Compo Sep 30 '20 at 11:33
  • @Compo Yeah I know, I mean I want to be able to work with those variables in the cmd.exe instance after i've run the script. – Ablia Sep 30 '20 at 14:44
  • @T3RR0R thanks a lot for your comments, it's clear and helps a lot. And you're right, I don't need the variable option. In fact, I now remember that I've had previously deleted it, dunno why I'm back with it...guess I'll delete it again and see the why ^^ But your second link is dead :/ – Ablia Sep 30 '20 at 14:48
  • To clarify @Ablia, you're running that script from an existing `cmd.exe` window, or another [tag:batch-file], is that correct? And to do that, you're using the `Call` command, is that correct? And this script does not use `Exit` without its `/B` option, is that correct? – Compo Sep 30 '20 at 15:17
  • @Compo yes, i'm running it from an existing cmd.exe windows, and this particular part of the script is accessed either with `call` or with `goto` (depends on which function access it). And this script doesn't use Exit at all. All the functions just end with `goto :eof` (or `goto :someotherfunc` for some) – Ablia Oct 01 '20 at 06:37
  • I asked about how you run the batch file @Ablia, not how you access a particular part of it. – Compo Oct 01 '20 at 10:05
  • @Compo sry misunderstood you here. Then it's just something like .\mybatch.cmd, from the cmd.exe windows. No `call` command here. – Ablia Oct 01 '20 at 12:03

1 Answers1

1

Well, your code should actually look like this:

rem // At this point, the default state should apply, hence no delayed expansion:
set /P "PROGNAME=enter the name of the prog: "
set /P "VERSION=enter the version of the prog: "
setlocal EnableDelayedExpansion
rem /* At this point, delayed expansion should be applied for all possible variables,
rem    because otherwise, you may run into problems with exclamation marks: */
if "!PROGNAME!"=="foo" (
    set "OPTIONS=(someParams)"
    rem /* You need `!OPTIONS!` here, of course, since it is set in the same block;
    rem    but you should also use `!PROGNAME!` and `!VERSION!` herein: */
    set "PROGPATH=C:\MyPath\!PROGNAME!!OPTIONS!_!VERSION!.exe"
) else if "!PROGNAME!"=="bar" (
    set "OPTIONS=(otherParams)"
    set "PROGPATH=C:\MyPath\!PROGNAME!!OPTIONS!_!VERSION!.exe"
)
rem /* For the sub-string substitution, you should avoid immediate expansion,
rem    because you may get in trouble with unbalanced quotes, if applicable;
rem    you could use a `for /F` loop to delay expansion of search/replace strings;
rem    as you can see, delayed expansion is used as much as possible, and
rem    the whole replacement expression is transferred to the `for` meta-variable: */
for /F "delims=" %%S in ("!PROGNAME!=!PROGNAME!dll") do set "PROGDLL=!PROGPATH:%%I!"
rem /* To avoid loss of set variables due to end of the environment localisation,
rem    use another `for /F` loop; delayed expansion is used as much as possible, and
rem    the whole assignment expression is transferred to the `for` meta-variable: */
for /F "delims=" %%E in ("PROGPATH=!PROGPATH!") do endlocal & set "%%E"

Note, that the set variables are only available in the same cmd.exe instance which the batch script runs in.


If you want to use call rather than delayed expansion, the code should be this:

rem // At this point, the default state should apply, hence no delayed expansion:
set /P "PROGNAME=enter the name of the prog: "
set /P "VERSION=enter the version of the prog: "
setlocal EnableDelayedExpansion
if "%PROGNAME%"=="foo" (
    set "OPTIONS=(someParams)"
    rem // You need `call` here, of course, since `OPTIONS` is set in the same block:
    call set "PROGPATH=C:\MyPath\%PROGNAME%%%OPTIONS%%_%VERSION%.exe"
    rem /* Here is an alternative way using `%%` for all variables, which may avoid
    rem    problems with unbalanced quotes, but you may get unwanted `^`-doubling: */
    rem call set "PROGPATH=C:\MyPath\%%PROGNAME%%%%OPTIONS%%_%%VERSION%%.exe"
) else if "!PROGNAME!"=="bar" (
    set "OPTIONS=(otherParams)"
    call set "PROGPATH=C:\MyPath\%PROGNAME%%%OPTIONS%%_%VERSION%.exe"
    rem call set "PROGPATH=C:\MyPath\%%PROGNAME%%%%OPTIONS%%_%%VERSION%%.exe"
)
rem // Of course you will need `call` for the sub-string substitution:
call set "PROGDLL=%%PROGPATH:%PROGNAME%=%PROGNAME%dll%%"

But regard, that this approach is slower, and you may get troubles with unwanted doubling of caret symbols ^. Furthermore, the %% could cause unintended expansion of for meta-variables, when, for instance, the code is placed inside a section where %%P is applicable, call set "PROGDLL=%%PROGPATH:…%%" will lead to unexpected results, because the %%P portion becomes expanded.

aschipfl
  • 28,946
  • 10
  • 45
  • 77