1

I've written a simple batch file to install a program under Windows 7. There's a maddening bug I'm running into with set /p VAR=PROMPT:

Input discarded bug

Here's a simple test case:

@echo off
setlocal

if exist "%USERPROFILE%\Documents" (
    set /p CONFIRM="Previous version is installed! Continue? (Y/n) "
    echo {%CONFIRM%}
)

if exist "%USERPROFILE%\Documents" (
    set /p CONFIRM="Previous version is installed! Continue? (Y/n) "
    echo {%CONFIRM%}
)

I run it:

Previous version is installed! Continue? (Y/n) n
{}
Previous version is installed! Continue? (Y/n) n
{n}

Notice that when the first set /p runs, it doesn't actually save my input!

Prompt mangling bug (related?)

I suspect, but am not sure, if this is related to a more obvious bug in set /p, wherein it truncates the prompt at the first ) if inside the parentheses defining the if block:

@echo off

if exist "%USERPROFILE%\Documents" (
    set /p CONFIRM=Previous version is installed! Continue? (Y/n)
    echo {%CONFIRM%}
)

At least this version captures my input, although it mangles the prompt:

Previous version is installed! Continue? (Y/nn
{n}

Workarounds?

Any ideas? Or is set /p just so buggy that I can't use it?

EDIT: Conclusions

Thanks to the links and tips from @npocmaka, I think I have a better idea what's going on: "parse time" vs. "run time" isn't the same inside a ()-delimited block as outside. Hence this example:

@echo off

(
    set /p CONFIRM=^?
    echo {%CONFIRM%}
)

(
    set /p CONFIRM=^?
    echo {%CONFIRM%}
)

echo {%CONFIRM%}

When run, it's obvious that there's a "delay" in the variables getting the expected values inside the blocks:

?foo
{}
?bar
{foo}
{bar}

Although delayed expansion works, I still believe this behavior is inconsistent with every description I've read of how set /p is supposed to work, though that's par for the course for batch files. It appears that variables set by set /p magically escape from parse-time expansion except when they appear inside blocks.

I believe the "prompt mangling" version only works (sorta) because it inadvertently closes the block early, and the batch file processor doesn't complain about the second mismatched ).

foxidrive
  • 40,353
  • 10
  • 53
  • 68
Dan Lenski
  • 76,929
  • 13
  • 76
  • 124
  • 1
    delayed expansion.... – npocmaka Aug 29 '14 at 23:10
  • npocmaka, adding `setlocal EnableDelayedExpansion` doesn't make a difference. Although I suspect this is a parser bug, it's not obviously related to variable expansion in any way. – Dan Lenski Aug 29 '14 at 23:13
  • 1
    when delayed expansion is used variables should be enclosed with `!` instead of `%` .At least the if they are within brackets. – npocmaka Aug 29 '14 at 23:15
  • Thanks, using `!CONFIRM!` does the trick. I still don't understand the root cause though, because without the enclosing `if` statement, it works fine (meaning that `%CONFIRM%` gets evaluated at run time, not parse time). – Dan Lenski Aug 29 '14 at 23:20
  • 2
    Here's exhaustive overview of how cmd parses scripts: http://stackoverflow.com/a/4095133/388389 . More fore delayed expansion - http://ss64.com/nt/delayedexpansion.html and http://www.robvanderwoude.com/variableexpansion.php – npocmaka Aug 29 '14 at 23:25
  • 1
    I edited the question title to reflect the problem so future readers may find it more easily. People ask questions that require delayed expansion every day - it's the most FAQ lately. – foxidrive Aug 30 '14 at 04:11

1 Answers1

1
@echo off
setlocal enableDelayedExpansion

if exist "%USERPROFILE%\Documents" (
    set /p CONFIRM="Previous version is installed! Continue? ^(Y/n^) "
    echo {!CONFIRM!}
)

if exist "%USERPROFILE%\Documents" (
    set /p CONFIRM="Previous version is installed! Continue? ^(Y/n^) "
    echo {!CONFIRM!}
)
Endlocal

Does this work?

npocmaka
  • 55,367
  • 18
  • 148
  • 187