I'm trying to adjust a Windows batch variable within a loop using the set /p command. After the keyboard input the variable still contains the old value. I have read that the variable set via set /p has only local scope. But I do not understand what "local" really means here.
#echo off
setlocal EnableDelayedExpansion
set a=4
echo Inital A: %a%
:LoopLabel
MODE | find %a% >nul 2>&1
IF %ERRORLEVEL% NEQ 0 (
set /p "a=enter new a: "
echo a=%a%
goto LoopLabel
)
The output is:
Inital A: 4
enter new a: 5
a=4
enter new a: 6
a=5
enter new a: 7
a=6
Does anyone have an idea and can me explain why this is happening?
Many Thanks,
Jonny
Executing code inside the IF is the problem. Check this out:
#echo off
set a=4
echo Inital A: %a%
:LoopLabel
MODE | find "%a%" >nul 2>&1
IF %ERRORLEVEL% EQU 0 goto stuff
set /p "a=enter new a: "
echo a=%a%
goto LoopLabel
:stuff
The set /p is executed outside of the IF, so it works fine even without delayed expansion.
All of the commands inside the IF block get evaluated at the same time (in parallel), so when
echo a=%a%
runs, it is unaware of any new value assigned by
set /p "a=enter new a: "
To further clarify, your a does in fact contain the new value, but the echo a=%a% was executed using the old value.
One solution is to use IF and labels to get the expected program flow, while avoiding multi-step execution within an IF block.
Also you're going to get an error from find unless either your entered value is quoted or you add quotes around %a% when you feed it to find.
Alternatively you can use delayed expansion on your a value to get its value "after" set /p has changed it.
To do this, you would change
echo a=%a%
to
echo a=!a!
The only variable scope that I know of in batch is the %1...%9 and %* in called labels. Otherwise everything is global, including your a set by set /p.
Related
I'm making a simple batch script to figure out arrays in batch script.
The code:
#echo off
setlocal EnableDelayedExpansion
set inputCount=0
set outputCount=0
:input
cls
set /p !number%inputCount%!=Input %inputCount%:
set /a inputCount=%inputCount%+1
if %inputCount% geq 3 goto output
goto input
:output
cls
echo !number%outputCount%!
set /a outputCount=%outputCount%+1
if %outputCount% geq 3 goto exit
goto output
:exit
pause
echo exit
On line 4, I set outputCount to 0, I then don't change outputCount until line 16 where I add 1 to it.
I expected the output of line 16 to be outputCount=0+1=1 therefore making outputCount=1. However, when I run the code with echo on to see exactly what it's doing, the output for line 16 is outputCount=2+1=3 setting outputCount to 3.
It seems that the program is setting outputCount to 2 instead of 0 at some point before line 16 but I can't see why.
First, take a look on Debugging a batch file as this is a lesson you need to learn on coding a batch file.
Second, read the answer on Why is no string output with 'echo %var%' after using 'set var = text' on command line? which offers additional information to the help output on running in a command prompt window set /?.
It looks like you want to define the environment variables number0, number1 and number2 with a string assigned to them by user input.
The command to use to prompt a user for a string is either
set /P "variable=prompt text: "
or
set /P variable="prompt text: "
The first variant is in general recommended although most often not used by batch file coding newbies because of not knowing how to use the double quotes right on assigning a string to an environment variable. The second variant is specific for set /P also possible and in some very rare cases really needed, but in my point of view should be avoided to use because of the double quotes are interpreted different on using set without /P.
So let us look on your code with commenting out with rem four lines, appending one more line with set at end of the batch file and run that batch file from within a command prompt window:
rem #echo off
setlocal EnableDelayedExpansion
set inputCount=0
set outputCount=0
:input
rem cls
set /p !number%inputCount%!=Input %inputCount%:
set /a inputCount=%inputCount%+1
if %inputCount% geq 3 goto output
goto input
:output
rem cls
echo !number%outputCount%!
set /a outputCount=%outputCount%+1
if %outputCount% geq 3 goto exit
goto output
:exit
rem pause
echo exit
set number
Output is at end just a line with NUMBER_OF_PROCESSORS=2. There are no environment variables number0, number1, number2 which would be also output by set number. And the command line echo !number%outputCount%! in file results three times in the information that ECHO is OFF.
The reason can be seen on looking on the output command lines really executed after preprocessing each line by Windows command interpreter.
set /p !number%inputCount%!=Input %inputCount%:
The string entered by the user, if not just RETURN or ENTER was hit by the user on prompt, should be assigned to the environment variables of which name are stored in the environment variables number0, number1 and number2. But the environment variables number0, number1 and number2 are never defined by your batch file as your intention is to store the input strings into the variables with name number0, number1 and number2. So this command line is finally on execution:
set /p =Input 0:
set /p =Input 1:
set /p =Input 2:
Those command lines would result in an exit of batch processing because of a syntax error, but this does not occur here because of usage of delayed expansion as it can be seen in the console window.
The solution is a batch code as follows:
#echo off
setlocal EnableExtensions EnableDelayedExpansion
set "Index=0"
:Input
cls
set /A Index+=1
set /P "Number%Index%=Input %Index%: "
if not %Index% == 3 goto Input
cls
set "Index=0"
:Output
set /A Index+=1
if defined Number%Index% echo Number%Index%=!Number%Index%!
if not %Index% == 3 goto Output
endlocal
The output of this batch file on entering on first prompt Hello!, on second prompt nothing and on third prompt Bye! is:
Number1=Hello!
Number3=Bye!
Okay, we have not entered a number as we have the freedom to type anything from nothing to bad strings like Double quote " or | or < or > are bad inputs on user prompt on being prompted for an input. But the batch file works as expected now.
Further please note that the string after set /A is an arithmetic expression which is parsed completely different to any other string on a command line. For examples the current values of environment variables can be referenced by using just the variable name without surrounding percent signs or exclamation marks. That make it possible to use variables in arithmetic expression within an IF or FOR command block without usage of delayed expansion. The help output on running set /? in a command prompt window explains this different parsing behavior quite good as well as which operators can be used like += in the arithmetic expression.
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
cls /?
echo /?
endlocal /?
goto /?
if /?
rem /?
set /?
setlocal /?
Some more hints:
Don't use just exit in a batch file, use exit /B or goto :EOF, see Where does GOTO :EOF return to?
After verification that the user entered anything at all and the entered string is really a number (decimal, octal or hexadecimal), make sure to process the number right according to your and the users' expectations. What I mean here is demonstrated with:
#echo off
set "Number=020"
set /A Number+=1
echo Result=%Number%
pause
What do you expect as result, 21 or the real output result 17?
A number string starting with 0 is interpreted as octal number. A number string starting with 0x or 0X is interpreted as hexadecimal number. Change 020 to 008 and the result is 1. Why? 008 is invalid for an octal number and therefore replaced by 0 which is incremented by one.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 6 years ago.
Improve this question
I was trying to create a simple program that would show up your name after you entered it.
After spending a few hours trying to get this batch file to work on a windows 10 and windows 7 computers, I can't still figure out what is the problem. After you input your answer whether or not you confirm that that is your name, it is still not working.
I've tried to debug by putting pause just about everywhere and it continues not to work.
Can somebody please point out what is wrong? Thanks.
Here is my code:
ECHO OFF
setlocal
COLOR A
cls
:getName
ECHO test
echo Please input name.
set "name="
SET /P NAME=
if not defined NAME goto getName
ECHO %NAME%, is this correct? Y/N
set /p 097=
if %O97%==Y goto :begin
set favvid=0
set hack=0
:b
echo Input name
set name=
set /P name=
echo %name%, correct? Y/N
set 897=
set /p 897=
if %897%==N goto :c
if %897%==Y goto :begin
:c
echo Input name
set name=
set /P name=
echo %name%, correct? Y/N
set 897=
set /p 897=
if %897%==N goto b
if %897%==Y goto begin
echo Name = %NAME% Is now your name. Too many attempts
:begin
echo Hello %Name%
pause
Avoid starting variable names with a number, this will avoid the
variable being misinterpreted as a parameter:
%123_myvar% in a batch script is parsed and then executed as %1 23_myvar
For proof, force echo ON.
And use (note that variable first character is not cipher zero but letter O.
set /p "O97=%name%, correct? Y/N "
if "%O97%"=="Y" goto :begin
rem note quoting in above commands
Consider using the CHOICE command as an alternative to SET /P (but accepts only one character/keypress).
Two errors on the if lines:
You should not use numbers to start variables in a batch file as they are interpreted as the arguments passed to the batch file. %0 is the batch file itself. %1 is the first, %8 is the 8th argument etc. (You can try echo --%0--%1--%097%-- in the batch file to see what result it gives you.)
Instead of this if "%097%"=="Y" goto begin
Use this if "%Answer%"=="Y" goto begin
Now also note that while your line set /p 097= has 097 when you
test for it in the next line with %O97% you do not have a 0 but
you have the letter O. This makes your test fail every time.
You need to put the string in quotes, like so:
if "%Answer%"=="Y" goto begin
Finally note that the test is case sensitive. So when the user presses y it will be interpreted as the wrong answer. You need to add a /I to your test:
if/I"%Answer%"=="Y" goto begin
One more point. After the goto you put colon some places, some places not, it is facultative but it is neater to keep it consistent.
I'm aware that set zeroThroughNine=%Random%*9/32768 followed by echo %zeroThroughNine% will produce a a random number between and including 0 and 9. But it seems the interpreter doesn't evaluate the contents of the variable every time it is called, and as such, echo %zeroThroughNine% produces, for example, 7 every time.
I looked up a method for running commands using variables so that I could try to force it to work. I liked the question because it was very basal in its approach; something along the lines of "How can I run commands using variables?", tagged appropriately. I didn't much care for the answer because it was very narrow. The highest voted and selected answer was:
Simple. Just run set commandVar=echo "Hello world.", followed by echo %commandVar%.
Of course the truth is that only works for the echo command. >: [
Anyway I'll stop complaining. This is what I've tried:
set zeroThroughNine=set /a number=%Random%*9/32768 & echo %number% followed by echo %zeroThroughNine%
Unfortunately the & echo %number% section of my SET command runs immediately, producing "%number%" as output --and using echo %zeroThroughNine% produces "set /a number=8436*9/32768", for example, as output.
So two questions: How can I universally achieve running commands with the use of variables (or some alternative method), and perhaps more pressing, how can I achieve producing a new random number at the command line with each new command calling?
You should set number before you set zeroThroughNine to the command, like so:
set /a number=%Random%*9/32768
set zeroThroughNine=echo %number%
%zeroThroughNine%
Also, since zeroThroughNine already is an echo command, you don't need to add the extra echo before it.
EDIT:
Taking into account your Random calculation is needlessly complicated, the final code should be something like this (1 - 10 exclusive):
set /a number=%Random% %% 10
set zeroThroughNine=echo %number%
%zeroThroughNine%
Important thing is, rather than trying to do it all on one line, it is much more readable by separating it into two.
The CALL SET syntax allows a variable substring to be evaluated, the CALL page has more detail on this technique, in most cases a better approach is to use Setlocal EnableDelayedExpansion.
Command line (note that all % percent signs are escaped as ^% and that > and & characters are escaped within a pair of " double quotes:
set "zeroThroughNine=call set /a number=^%Random^% ^% 10>nul & call echo number=^%number^%"
%zeroThroughNine%
for /L %G in (1, 1, 10) do #%zeroThroughNine%
Batch script, CALL method (note that all % percent signs are escaped as %%):
#echo OFF
SETLOCAL
set "_zeroThroughNine=call set /a _number=%%Random%% %%%% 10 & call echo number=%%_number%%"
echo check variables
set _
echo output
%_zeroThroughNine%
for /L %%G in (1,1,10) do %_zeroThroughNine%
echo check variables after evaluating
set _
ENDLOCAL
Batch script, EnableDelayedExpansion only for output:
#echo OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set "_zeroThroughNine=set /a _number=!Random! %% 10 & echo Number=!_number!"
SETLOCAL EnableDelayedExpansion
echo check variables
set _
echo output
%_zeroThroughNine%
for /L %%G in (1,1,10) do %_zeroThroughNine%
echo check variables after evaluating
set _
ENDLOCAL
ENDLOCAL
Batch script, EnableDelayedExpansion script wide (note that ! exclamation sign is escaped as ^!):
#echo OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
set "_zeroThroughNine=set /a _number=^!Random^! %% 10 & echo NUMBER=^!_number^!"
echo check variables
set _
echo output
%_zeroThroughNine%
for /L %%G in (1,1,10) do %_zeroThroughNine%
echo check variables after evaluating
set _
ENDLOCAL
Check out this question. Basically, it's an issue with how the %random% environment variable works...
EDIT:
To elaborate, the reason your random value was always 7 is because of how cmd's pseudo-random number generator works, not because of how the variables are interpreted. The matter is explained very well in this answer.
Essentially, in repeated runs of a batch file, %RANDOM% will produce a value very close to the previous run. Thus, the expression %RANDOM%*9/32768 produces the same result in each separate run because of the random value.
If I understand correctly, the question you're asking is how to better generate a random value 0 - 9 inclusive, which would be by using the following expression:
set /a zeroThroughNine=%RANDOM% %% 10
The below code works, echo test.test
set replaceWith=.
set str="test\test"
call set str=%%str:\=%replaceWith%%%
echo %str%
But, the below code echo ggg.hhhhh all the 4 times.
SET SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)
for %%i in %SERVICE_LIST% do (
set replaceWith=.
set str="%%i"
call set str=%%str:\=%replaceWith%%%
echo %str%
)
What am I doing wrong here?
If you understand why your code uses call set str=%%str:\=%replaceWith%%%, then you should be able to figure this out ;-)
Syntax like %var% is expanded when the line is parsed, and your entire parenthesized FOR loop is parsed in one pass. So %replaceWith% and echo %str% will use the values that existed before you entered your loop.
The CALL statement goes through an extra level of parsing for each iteration, but that only partially solves the issue.
The first time you ran the script, you probably just got "ECHO is on." (or off) 4 times. However, the value of str was probably ggghhhhh and replaceWith was . after the script finished. You don't have SETLOCAL, so when you run again, the values are now set before the loop starts. After the second time you run you probably got ggghhhhh 4 times. And then from then on, every time you run the script you get ggg.hhhhh 4 times.
You could get your desired result by using CALL with your ECHO statement, and moving the assignment of replaceWith before the loop.
#echo off
setlocal
SET SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)
set "replaceWith=."
for %%i in %SERVICE_LIST% do (
set str="%%i"
call set str=%%str:\=%replaceWith%%%
call echo %%str%%
)
But there is a better way - delayed expansion
#echo off
setlocal enableDelayedExpansion
SET "SERVICE_LIST=aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh"
set "replaceWith=."
for %%i in (%SERVICE_LIST%) do (
set str="%%i"
set str=!str:\=%replaceWith%!
echo !str!
)
Please have a text book for Windows Command Shell Script Language and try this:
#ECHO OFF &SETLOCAL
SET "SERVICE_LIST=(aaa\bbb ccc\dddd eeee\fffff ggg\hhhhh)"
for /f "delims=" %%i in ("%SERVICE_LIST%") do (
set "replaceWith=."
set "str=%%i"
SETLOCAL ENABLEDELAYEDEXPANSION
call set "str=%%str:\=!replaceWith!%%"
echo !str!
ENDLOCAL
)
I have a code that is shared by 6 different bat scripts below that takes an input argument. I wonder if I can externalize this piece in a seperate bat script and import it instead, so everytime I update this piece of code, I don't have to update all 6 bat scripts.
Code:
:Loop
IF "%1"=="" GOTO Prompt
SET VAR=%1
GOTO Continue
SHIFT
GOTO Loop
:Prompt
set /p VAR="Check which value? "
GOTO Continue
:Continue
Yes, using redirection.
Take this solution.bat file
set /p myvar=< somestring.txt
Where somestring.txt contains "abc"
myvar will now exist as an environment variable with abc.
Your code is supposed to set VAR to the first argument. If the first arg is missing then you want to prompt for a value.
First off, I would simplify your logic.
set "VAR=%~1"
if not defined VAR set /p "VAR=Check which value? "
Once simplified like above, I don't see why you would feel a need to externalize the code. But it could be done.
In your main script
call getArg.bat %1
And here is getArg.bat
set "VAR=%~1"
if not defined VAR set /p "VAR=Check which value? "
exit /b