Batch file variable scope issue - batch-file

I'm having a strange variable scope issue when trying to create a 'dos' (windows 7 command line) batch file which performs a bit of string manipulation to create new file paths. Can anyone see why the OUTPUT_FILENAME variable always ends up being null in the example below?
echo Enter the Data Input, S (Site) or U (User)
set /p DATA_TYPE=
echo.
echo Enter the Input File Name
set /p INPUT_FILENAME=
echo.
IF /I %DATA_TYPE%==u (
set OUTPUT_FILENAME=%INPUT_FILENAME:\users\=\Users\Outputs\%
set OUTPUT_FILENAME=%OUTPUT_FILENAME:xls=txt%
echo Output:
echo %OUTPUT_FILENAME%
)
IF /I %DATA_TYPE%==s (
set OUTPUT_FILENAME=%INPUT_FILENAME:\sites\=\Sites\Outputs\%
set OUTPUT_FILENAME=%OUTPUT_FILENAME:xls=txt%
echo Outputs:
echo %OUTPUT_FILENAME%
)
Thanks in advance for any assistance, this is driving me nuts!

You need to enable the delayed expansion:
setlocal EnableDelayedExpansion
echo Enter the Data Input, S (Site) or U (User)
set /p DATA_TYPE=
echo.
echo Enter the Input File Name
set /p INPUT_FILENAME=
echo.
SET OUTPUT_FILENAME=Empty
IF /I %DATA_TYPE%==u (
set OUTPUT_FILENAME=!INPUT_FILENAME:\users\=\Users\Outputs\!
set OUTPUT_FILENAME=!OUTPUT_FILENAME:xls=txt!
echo Output:
echo !OUTPUT_FILENAME!
)
IF /I %DATA_TYPE%==s (
set OUTPUT_FILENAME=!INPUT_FILENAME:\sites\=\Sites\Outputs\!
set OUTPUT_FILENAME=!OUTPUT_FILENAME:xls=txt!
echo Outputs:
echo !OUTPUT_FILENAME!
)
As the help for the SET command states:
Delayed environment variable expansion is useful for getting around the limitations of the current expansion which happens when a line of text is read, not when it is executed.
So, you need to use the delayed expansion to make sure that INPUT_FILENAME OUTPUT_FILENAME's value are expanded at execution time.

As Laf has correctly indicated, the code, as is, needs delayed expansion. In batch files, when a line or a block (all the lines enclosed in parenthesis) is reached, before being executed, it is parsed. In this parse phase, each variable read is replaced with the value the variable has before the execution starts.
If inside a block you change a variable, and want to access this changed value inside the same block, you need delayed expansion. The code in Laf answer reflect how to do it
Or, if it is possible, you can change your code to not need it
echo Enter the Data Input, S (Site) or U (User)
set /p DATA_TYPE=
echo.
echo Enter the Input File Name
set /p INPUT_FILENAME=
echo.
IF /I %DATA_TYPE%==u (
set OUTPUT_FILENAME=%INPUT_FILENAME:\users\=\Users\Outputs\%
)
IF /I %DATA_TYPE%==s (
set OUTPUT_FILENAME=%INPUT_FILENAME:\sites\=\Sites\Outputs\%
)
set OUTPUT_FILENAME=%OUTPUT_FILENAME:xls=txt%
echo Output:
echo %OUTPUT_FILENAME%
Now, there are variables changed inside blocks, but the values are then accessed out of the blocks.

Related

set /a in If statement condition unable to work

Below are my simple calculator batch i trying to do, however on the set /a unable to do the job. What is the problem and my mistake?
#echo off
Title Calculator
:start
echo Press 1 for "+"
echo Press 2 for Exit
echo.
set /p input="Please choose your option: "
if %input%==1 (
echo.
set /p num1="Please enter first number: "
set /p num2="Please enter second number: "
set /a ans=%num1%+%num2%
echo Your answer is %ans%
pause
cls
goto start
)
if %input%==2 (
echo.
echo Thanks!
pause
exit
) else echo Invalid input!
pause
goto start
When i first run the batch is will return me the Missing operand. When i continue again the error disappear without giving me the answer, when the third time i continue, it return me the answer of the number that i wanted to add up.
For example:
1. 10+10 result is Missing operand
2. 1+1 result is empty
3. 2+2 result is 20 (which is the 2 number i enter at first time)
Please help what my error is. Thanks.
Here is your batch code with using delayed expansion and indents as wisely suggested by rojo:
#echo off
title Calculator
setlocal EnableExtensions EnableDelayedExpansion
:Begin
echo Press 1 for "+"
echo Press 2 for Exit
echo/
set "input="
set /P "input=Please choose your option: "
if "!input!"=="1" (
echo/
set /P "num1=Please enter first number: "
set /P "num2=Please enter second number: "
set /A ans=num1+num2
echo Your answer is !ans!
pause
cls
goto Begin
)
if "!input!"=="2" (
echo/
echo Thanks^^!
echo/
pause
exit /B
)
echo Invalid input^^!
echo/
pause
echo/
goto Begin
Variable input is always cleared before user is asked because otherwise the user could just hit key RETURN or ENTER to keep current value of variable input.
Delayed expansion is used on checking user input against 1 or 2 in case of user enters a character which would result in a syntax error on execution with not using delayed expansion. Better would be nevertheless the usage of command choice for first user prompt.
For an expression evaluated on runtime by using set /A the environment variables can be specified directly without being expanded at all. So instead of using
set /A ans=%num1%+%num2%
or
set /A ans=!num1!+!num2!
it is enough to write
set /A ans=num1+num2
because with parameter /A command set interprets num1 and num2 automatically as names of variables.
Delayed expansion is nevertheless needed to print the result value stored in variable ans because command processor expands otherwise %ans% by nothing respectively the value of previous run on parsing the entire block starting with ( and ending with ). This can be seen on running your batch file from within a command prompt window with first line changed to #echo on for debugging.
For more details run in a command prompt window set /? or help set and read the output help pages.
start is the name of a command. Therefore it is not good to use this word as name of a label although possible.
By the way: Always use set "variable=value" and never set variable="value" as this makes a big difference, see the answers on
Why is no string output with 'echo %var%' after using 'set var = text' on command line?
How to set environment variables with spaces?

Clear variables values in batch

I am reading two text files to set the values of two variables (u,l). Now I want to write script to run multiple files. When it is reading first file it will set the variables from the respective files but when it is reading second file it will set the same values of those variables.
#echo on
set /p u=< ul.txt
set /p l=< ll.txt
echo %u%-%l%
I tried SETLOCAL/ENDLOCAL option but in that case it is not reading variables values and getting error that ECHO is off. Even I wrote set u= and set l= at the initial of the script but not working in my case.
Your code, as given, works fine. However, I'm guessing it is code from inside an if statement, or for loop. If that is the case, you should use delayed expansion. You can use delayded expansion like this:
This is an example, not the exact code you need:
#echo on
setlocal EnableDelayedExpansion
if 1 equ 1 (
set /p "u=< ul.txt"
set /p "l=< ll.txt"
echo !u!-!l!
)
pause
FOR /L %%G IN (1,1,1) DO (
set /p "u=< ul.txt"
set /p "l=< ll.txt"
echo !u!-!l!
)
pause
set /p u=< ul.txt
set /p l=< ll.txt
echo %u%-%l%
pause
Note that inside the if statement and for loop, you replace % signs, when they are around variable names, with !. So %someVar% becomes !someVar!, but %%F stays %%F.
Outside of if statements and for loops, so outside of (), you use the normal %someVar%

Is there anyway to have preset data for user input in a batch file?

So basically I have a batch file that requires alot of user input. I was wondering if it was possible to have any filler data already present when the question is asked, and if the user needs to change something, they can go edit that data. For example
And then the user enter their first and last name.
But is it possible to start with a default name that the user can go back and edit if they need?
This probably isn't necessary, But this is the code I use for the user input.
Set /p "Author=Please enter your name: "
And I understand for the Author it wouldn't make much sense to have preset data, but there are other instances where it would be useful for me to do this. Is it possible?
nearly impossible to edit a preset value with pure batch, but you can easily give a default value (works, because set /p is not touching the variable, if input is empty)
set "author=First Last"
set /p "author=Enter name or press [ENTER] for default [%author%]: "
echo %author%
The method below have the inconvenience that the screen must be cleared before the user enter the data, but I am working trying to solve this point:
EDIT: I fixed the detail of the first version
#if (#CodeSection == #Batch) #then
#echo off
rem Enter the prefill value
CScript //nologo //E:JScript "%~F0" "First Last"
rem Read the variable
echo -----------------------------------------------------------
set /P "Author=Please enter your name: "
echo Author=%Author%
goto :EOF
#end
WScript.CreateObject("WScript.Shell").SendKeys(WScript.Arguments(0));
For further details, see this post.
You can set the var first and then prompt the user only if it's not defined like so:
set Author=First Last
if not defined Author set /p "Author=Please enter your name: "
You can also do this backwards where you can define a value if the user didn't define it, like so:
set /p "Author=Please enter your name: "
if not defined Author set Author=First Last
There Is another way yet to achieve this. It uses vbs script to get input and assign it to a variable, -The script can be created within and called from .bat files.
I developed this method as an alternative to accepting user input from set /p in order to validate input and prevent setting of variables with spaces or special characters.
*Validation methods omitted as does not relate to the question
Best method is to have the script generator as a secondary .bat file that you can call, as it allows for a lot of versatility regarding the information the vbs input box conveys, as well as being able to be used in any circumstance within one or more .bat files you want to be able to control input defaults with.
In your main program, when Preparing to get input-
REM Define title:
Set inputtitle=The title you want the input box to have
REM Declare variable to be assigned:
Set variableforinput=VariableName
REM Define the default Variable Value:
Set defaultinput=VariableName's Desired Default Value
REM getting the Input:
CALL "inputscript.bat" %inputtitle% %variableforinput% %defaultinput%
inputscript.bat:
#echo off
SET inputtitle=%~1
SET variableforinput=%~2
SET defaultinput=%~3
SET %variableforinput%=
SET input=
:main
REM exit and cleanup once variable is successfully defined
IF DEFINED input GOTO return
REM this creates then starts the VBS input box, storing input to a text file.
(
ECHO Dim objFSO 'File System Object
ECHO Set objFSO = CreateObject("Scripting.FileSystemObject"^)
ECHO Dim objTS 'Text Stream Object
ECHO Const ForWriting = 2
ECHO Set objTS = objFSO.OpenTextFile("%userprofile%\getinput.txt", ForWriting,
True^)
ECHO objTS.Write(InputBox("Please enter %variableforinput% to continue.","%inputtitle%","%defaultinput%",0,0^)^)
ECHO objTS.Close(^)
ECHO Set bjFSO = Nothing 'Destroy the object.
ECHO Set objTS = Nothing 'Destroy the object.
) >GetInput.vbs
START GetInput.vbs
REM a pause that allows time for the input to be entered and stored is required.
cls
ECHO (N)ext
CHOICE /T 20 /C nz /N /D n >nul
IF ERRORLEVEL ==2 GOTO main
IF ERRORLEVEL ==1 GOTO loadinput
:loadinput
IF NOT EXIST "%userprofile%\getinput.txt" GOTO invInput
<%userprofile%\getinput.txt (
SET /P input=
)
IF NOT DEFINED input GOTO invInput
REM opportunity for other validation of input before returning to main.
GOTO main
:invInput
SET input=
IF EXIST "GetInput.vbs" (
DEL /Q "GetInput.vbs"
)
REM ends the vbs script ready for the next attempt to provide input
taskkill /pid WScript.exe /T >nul
Timeout 1 >nul
GOTO main
REM assigns the input value to the variable name being set in Your Main program.
:return
SET %variableforinput%=%input%
SET input=
IF EXIST "%userprofile%\getinput.txt" (
DEL /Q "%userprofile%\getinput.txt"
)
IF EXIST "GetInput.vbs" (
DEL /Q "GetInput.vbs"
)
GOTO :EOF
I wrote an open-source Windows console program called editenv that replaces my older editv32/editv64/readline.exe utilities:
https://github.com/Bill-Stewart/editenv
Basically, editenv lets you interactively edit the value of an environment variable. One of my common use cases is to edit the Path environment variable for the current process:
editenv Path
editenv also provides the following convenience features:
--maskinput allows you to hide the typed input (note that this feature does not provide any encryption or security)
--allowedchars and --disallowchars allow you to specify which characters are allowed for input
--minlength and --maxlength let you choose a minimum and maximum length of the input string
--timeout lets you specify a timeout after which input is entered automatically
The most recent binaries are available here:
https://github.com/Bill-Stewart/editenv/releases
askingFile.cmd < response.txt
Take the input to the batch from the indicated file, one line per answer

batch script, renaming a xml file inside a folder

I am writing a batch file whereby I can rename a xml file name inside a folder on my Desktop.
prompt the user to key in the file name
if the file name is found, it will then prompt the user to key in an existing xml file name
if an existing xml file name, it will then prompt the user to key in a new xml file name
an overview of my contents inside a file name(bb).
this is my batch-script
#echo off
set /p fn=Enter Folder name:
if exist C:\Users\roberts\Desktop\%fn% (
set /p oldxmlname=Enter a old xml file name:
if exist C:\Users\roberts\Desktop\%fn%\%oldxmlname%.xml (
set /p newxmlname=Enter a new xml file name:
ren C:\Users\roberts\Desktop\%oldxmlname%.xml %newxmlname%.xml
echo file name changed successfully.
)
)else (
echo folder not found in path.
echo C:\Users\roberts\Desktop\%fn%
)
this is my output. The file name didn't change at all.
Within a block statement (a parenthesised series of statements), the entire block is parsed and then executed. Any %var% within the block will be replaced by that variable's value at the time the block is parsed - before the block is executed.
Hence, IF (something) else (somethingelse) will be executed using the values of %variables% at the time the IF is encountered. The same comment goes for the REN.
Two common ways to overcome this are 1) to use setlocal enabledelayedexpansion and use !var! in place of %var% to access the changed value of var or 2) to call a subroutine to perform further processing using the changed values.
And if an ELSE clause is used, the IF-true command must be (parenthesised), and the ) and ELSE must be on the same physical line. If ELSE-true command is (parenthesised), the ELSE and ( must be on the same physical line. There must be a Space either side of the else keyword.
A demonstration:
#echo off
SETLOCAL
cls
set var=ORIGINAL
echo Start value of var=%var%
for %%i in (1) do (SET var=AFTER
ECHO var is %var% in the for loop
)
echo Final value of var=%var%
ENDLOCAL
ECHO.
echo ---- try again with ENABLEDELAYEDEXPANSION
ECHO.
setlocal enabledelayedexpansion
set var=ORIGINAL
echo Start value of var=%var%
for %%i in (1) do (SET var=AFTER
ECHO var is %var% not !var! in the for loop
ECHO Note: in the loop VAR has old value %var% and new value !var!
)
echo Final value of var=%var%
endlocal
Note how the value of var is interpreted differently depending on the use of setlocal or setlocal enabledelayedexpansion. The !var! syntax is only available in delayedexpansion mode and accesses the value of var as it is modified in the block,
You left \%fn% out of the ren command, so the file you're asking to rename really doesn't exist.
You want ren C:\Users\roberts\Desktop\%fn%\%oldxmlname%.xml %newxmlname%.xml
Here is a method that doesn't need delayed expansion, which your code does.
When changing a variable and using it within a loop, then delayed expansion is required.
There are some quotes to protect long file/path elements and an added backslash to detect a path and not a file.
#echo off
set /p fn=Enter Folder name:
if not exist "C:\Users\roberts\Desktop\%fn%\" (
echo The engines canna take the strain captain, try again!
goto :EOF
)
set /p oldxmlname=Enter a old xml file name:
if not exist "C:\Users\roberts\Desktop\%fn%\%oldxmlname%.xml" (
echo folder not found in path.
echo "C:\Users\roberts\Desktop\%fn%"
goto :EOF
)
set /p newxmlname=Enter a new xml file name:
ren "C:\Users\roberts\Desktop\%oldxmlname%.xml" "%newxmlname%.xml"
if not errorlevel 1 (
echo file name changed successfully.
) else (
echo oops! Something is fubar
)

Batch file input not working correctly when in if block

When using set /p inside this if block. The variable input is not set to the input value. It is set only on second invocation of the script (as if it was set only after echo %input% line).
if "%1"=="" (
echo "You have to specify the name of the file."
set /p input=File name:
echo %input%
pause
) else (
...
)
What can I do to have variable input set to values which was actually entered?
you don't need delayed expansion here. Example:
if "%1"=="" (
echo "You have to specify the name of the file."
set /p input=File name:
call echo %%input%%
pause
) else (
...
)
You need to use delayed expansion.
In the Batch language, inside a FOR or a IF, the variables are "expanded" before and not during command execution. ( expanded = the variable is remplaced by its value )
For example, the following IF test
IF condition (
foo
bar
)
is interpreted as if condition foo & bar
So if a variable is set in foo and this same variable is used in bar, it's the previous value of the variable ( the one before entering the loop ) that is used in bar.
It's kinda disturbing but that how Batch works...
So ithe set is working correctly, it's just a special way of working.
You must write SETLOCAL ENABLEDELAYEDEXPANSION at the beginning of your code and the variable whose expansion should be delayed must be surrounded by ! instead of %.
So echo %input% become echo !input!

Resources