Assigning a variable inside if loop : Windows Bat script - batch-file

I'm trying to write a bat script and faced the following issue.
Below is the snippet with which the issue is dealt with.
setlocal enabledelayedexpansion enableextensions
#echo off
#echo:
echo Select an option:
echo *****************
#echo:
echo 1) process 2gen files
#echo:
echo 2) process 3gen files
#echo:
set /p option=Enter option number : %=%
set fileName=nil
echo %option%
if %option% == 1 (
set fileName = 2gen
echo !fileName!
)
echo !fileName!
The output of this bat script is as follows:
Select an option:
*****************
1) process 2gen files
2) process 3gen files
Enter option number : 1
1
nil
nil
i'm expecting the echo for fileName to have value 2gen but it is still printing nil
In my first attempt, i didn't use the setlocal enabledelayedexpansion enableextensions and !fileName! (instead used %fileName%)
After searching for solution enabled the delayed expansion. But still couldn't get what the exact mistake is.
Also the OS version being used is Windows 7 64 bit
Please help me in pointing out the mistake.
Thanks!

set fileName = 2gen sets a variable named filename<space> to a value <space>2gen. Get rid of the spaces. Even better, use this recommended syntax:
set "variable=value"
to avoid unintended trailing spaces.
(of course you could use echo !filename !...)

Related

How to save user input from CHOICE command to a variable to be used later in the errorlevel

I have a dynamic list of choices for a program I'm writing. I have it working correctly so that it will change the CHOICE options based on the count variable but now I'm struggling with making the errorlevel dynamic as well. Here is my code:
SETLOCAL EnableDelayedExpansion
#ECHO off
SET count=7
SET ph=
FOR /L %%a IN (1,1,%count%) DO (
SET ph=!ph!%%a
ECHO !ph!
)
CHOICE /C Q%ph%
IF errorlevel (I don't have a variable for this) (
echo "in if" & pause
)
IF errorlevel 1 echo "out of if" & pause
My idea is to set the errorlevel equal to what the user put in (e.g. the user puts in 7 as their choice, the errorlevel becomes 7) The reason I want to do this is because I need the errorlevel to pass for everything besides 1, which is reserved for a quit option (which is why I have the "Q" there) Any advice and suggestions are much appreciated! Thanks!
I suggest following batch code for this task:
#ECHO OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
SET count=7
SET ph=
FOR /L %%a IN (1,1,%count%) DO SET ph=!ph!%%a
%SystemRoot%\System32\choice.exe /C Q%ph%
IF NOT ERRORLEVEL 2 ECHO Bye^^!& GOTO :EOF
SET /A UserChoice=%ERRORLEVEL%-1
ECHO You have chosen %UserChoice%.
PAUSE
IF NOT ERRORLEVEL 2 means if the exit code of CHOICE is NOT GREATER OR EQUAL 2 which is the same as if LESS THAN 2 then execute ECHO and GOTO to exit processing of this batch file.
The command IF does not modify value of ERRORLEVEL as documented at
What are the ERRORLEVEL values set by internal cmd.exe commands?
It would be also possible to first assign the exit code of CHOICE to an environment variable decremented by 1 and then make the comparison for quit by comparing the value with 0.
#ECHO OFF
SETLOCAL EnableExtensions EnableDelayedExpansion
SET count=7
SET ph=
FOR /L %%a IN (1,1,%count%) DO SET ph=!ph!%%a
%SystemRoot%\System32\choice.exe /C Q%ph%
SET /A UserChoice=%ERRORLEVEL%-1
IF %UserChoice% == 0 ECHO Bye^^!& GOTO :EOF
ECHO You have chosen %UserChoice%.
PAUSE
It is not advisable to use an environment variable with name choice as it makes it difficult to search for this environment variable in a batch file containing also external command CHOICE which is the reason for using UserChoice.
The command CHOICE is specified with full qualified file name (file path + file name + file extension) for safety reasons. Windows command processor does not need to search for choice.* with a file extension listed in environment variable PATHEXT in current directory and the directories listed in local environment variable PATH on using full qualified file name. This makes the batch file robust against corrupted system PATH containing the path of a folder before most important folder path %SystemRoot%\System32 which by chance contains also a choice.* file with a file extension listed in PATHEXT. The local environment variable PATH does not need to exist at all on running this batch file because of using full qualified file name of executable CHOICE. It also does not matter with full qualified file name if a user created a batch file with name choice.bat or choice.cmd in the directory being the current directory on running this batch file or any other directory in PATH being searched by cmd.exe before %SystemRoot%\System32.
To solve my issue I used the NEQ and EQU statements that I was not aware would work with errorlevel. I also incorporated the suggestion of #LotPings to get this code as a result which works great! To technically save the user input as a variable, I just subtracted one from the choice variable and it equals the same.
SETLOCAL EnableDelayedExpansion
REM #ECHO off
SET count=7
SET ph=
FOR /L %%a IN (1,1,%count%) DO (
SET ph=!ph!%%a
REM ECHO !ph!
)
CHOICE /C Q%ph%
set choice=%errorlevel%
ECHO %choice%
IF %choice% NEQ 1 (
ECHO "not equal" & PAUSE
SET /A choice=choice-1
ECHO !choice! REM This is now the value the user inputted.
PAUSE
)
IF %choice% EQU 1 ECHO "equal" & PAUSE

A script that counts and prints every ocurrence of not any file inside a common subfolder in a specific path

Although I'm really a newbie in this field, I want to accomplish a task in batch scripting: There is a determinate folder of company contracts in a determinate path, each of this folders (approx. 400) has a common folder (2016) where there might be a file indicating there has been an inspection in this year. What i want is to print every company folder that has not any file in the common 2016 folder and a count of the times this happens.
This is what i have (and does not work at all):
set c=0
for %i /d in (*) do
for %j in ($%i\2016\*) do
if (%j==NUL) then (#echo $%i c+=1 echo %c)`
If you just want to know if there is a file in the 2016 directory you can do this:
#echo off
SetLocal EnableDelayedExpansion
set count=0
for %%i /d in (*) do (
REM first unset variable
set files=
for %%j in (%%i\2016\*) do (
REM will set variable each time a file is encountered
set files=present
)
if not DEFINED files (
REM No files in directory 2016
echo %%i
set /a count+=1
echo !count!
)
)
EndLocal
exit /b 0
I don't see why you use $ before each %i. If you execute this code from the command line use one % for the loop variables i and j. But in a batch-script you'll have to use two of them (%%i, %%j).
Another thing, c+=1 won't work except if you use set /a.
I used delayed expansion because each block code ( between (...)) is parsed as one single command (as if it was all on one line with && between the commands inside the block) and you can't just assign a new value to a variable and read that new value in the same command. That's also the reason why I use !count! instead of %count% (which will give the value before the block). If you'd rather not use delayed expansion, remove the SetLocal EnableDelayedExpansion and replace echo !count! with call echo %%count%% (is another way to read a new value in the same command)
Also, be aware that each echo will end its output with a carriage retur and a newline. So each echo will result in a new line of output.

'Batch" Issue with file names having &'s in them

Not quite sure how to title my issue that I'm running into (tried as best I could), but what I'm having issues with is when I'm trying to read in file names and then use them in a backup script I've wrote. I had originally tested it on files without &'s in the name (didn't remember that I had any with them in it, and didn't realize that there would be an issue until now).
Here is part of the code that is being used upto the call in the below example:
:backup2
if %DEBUG%=="t" echo Begin backup part 2
for %%? in ("!FILEREADIN!") do (
SET "FILENAME=%%~n?%%~x?"
SET "BACKUPFQP=%%~f?"
SET "BACKUPLAST=%%~t?"
call :getlength FILELENGTH "!FILENAME!"
Anyway the part that I'm running into the issue with when I'm working with the file name in my code to get the length of the file name (used in a separate section of script).
:getlength
SETLOCAL enabledelayedexpansion
if %DEBUG%=="t" echo %2 parsed... %%2 delayed... !%2!
SET "LENGTH=!%1!"
SET "STRING=%2"
REM need to correct the string for the "" that get added from passing in %2
REM Issue arises with this part below when working on a file name with a &
SET "STRING=!STRING:~1,-1!"
:getlengthwhile
if %DEBUG%=="t" echo Length !LENGTH!
if %DEBUG%=="t" echo String left: !STRING!
SET /a "LENGTH+=1"
REM Issue here too when working with file names with &'s
SET "STRING=%STRING:~1%"
if %DEBUG%=="t" echo Length now !LENGTH!
if %DEBUG%=="t" echo String now left: !STRING!
if %DEBUG%=="t" pause
if not ["%STRING%"]==[""] (
if %DEBUG%=="t" echo Continuing source length calculation
Goto :getlengthwhile
) else (
if %DEBUG%=="t" echo Length calculated)
ENDLOCAL & SET TEMPNUM=%LENGTH%
SET "%~1=%TEMPNUM%"
if %DEBUG%=="t" echo !%1!
if %DEBUG%=="t" pause
goto :eof
I know that there is escaping &'s to normally not have the error of the batch script trying to use the stuff right of the & as a command, but when reading in a file with one (or more) in its name how do I get it to work properly?
Here's an example of a file name that I'm having issue with and what happens when I'm running my script:
File "E:\Projects\.\Abilities&Events.docx"
Press any key to continue . . .
Begin backup part 2
"Abilities&Events.docx" parsed... %2 delayed...
'Events.docx""' is not recognized as an internal or external command, operable program or batch file.
As said, you should use more quotes and more delayed expansion.
Btw. Your code add the length to the variable in %1, perhaps this was intended, else you should change SET "LENGTH=!%1!" to set LENGTH=0
:getlength
SETLOCAL Enabledelayedexpansion
SET "LENGTH=!%1!"
SET "STRING=%~2"
:getlengthwhile
if defined STRING (
set /a LENGTH+=1
set "string=!string:~0,-1!"
goto :getlengthwhile
)
echo !LENGTH!
(
ENDLOCAL
SET "%~1=%LENGTH%"
)
goto :eof
Another problem of your code is this line
call :getlength FILELENGTH "!FILENAME!"
It fails with filenames containing ^, as they are doubled by the CALL.
So it's better to use
call :getlength FILELENGTH FILENAME
...
:getlength
SETLOCAL Enabledelayedexpansion
SET "LENGTH=!%1!"
SET "STRING=!%2!"
For much faster strlen functions you could look at SO: How do you get the string length in a batch file?
I found an answer to my problem. Thanks all for the help.
My solution was after finding out that I can replace parts of a variable (which I can use to delete parts of them even) from this site: http://ss64.com/nt/syntax-replace.html
I didn't know that replacing could be done (I knew that getting a part of a variable was possible (site for reference: http://ss64.com/nt/syntax-substring.html"))
I ended up ditching my getlength function (since it was no longer needed). and ended up with something like this:
:backup2
if %DEBUG%=="t" echo Begin backup part 2
for %%? in ("!FILEREADIN!") do (
SET "FILENAME=%%~n?%%~x?"
SET "BACKUPFQP=%%~f?"
SET "BACKUPLAST=%%~t?"
if %DEBUG%=="t" echo Filename before "!FILENAME!"
SET FILETESTPART=!FILEREADIN!
if %DEBUG%=="t" echo !FILETESTPART!
SET "FILETESTPART=!FILETESTPART:%SOURCE%=!"
if %DEBUG%=="t" echo Filename after "!FILETESTPART!"
SET "FILETESTPART=!FILETESTPART:~3!"
if %DEBUG%=="t" echo Filename after "!FILETESTPART!"

quotes in Windows environment variable value

I'm trying to implement, in a Windows batch file, the logic "if %MyBinaryDir% is not already at the beginning of the system %PATH%, then put it there; if it's already there, do nothing".
I've got this:
#echo %PATH% | findstr /i /b /c:"%MyBinaryDir%;" > nul || set "PATH=%MyBinaryDir%;%PATH%
This has always worked pretty well, until I tried to deploy on someone's machine where for some unearthly reason the %PATH% variable contained an odd number of quote characters.
The problem boils down to this:
#set x="
#echo %x%
#echo %x% | more
The second line prints a single " character. The third line is tripped up by the quote character and fails to pipe the echo output to the second binary (in my case findstr, but in this boiled-down example more) at all. Instead, it literally prints the characters:
" | more
So my questions are:
(Y) How do I safely pipe any arbitrary string into a second binary?
and/or
(X) Is there a way of conditionally-prepending directories to the system path that avoids this mess?
Use delayed expansion to echo any variable content in a safe way.
setlocal EnableDelayedExpansion
set x="
echo !x!
echo !x! | more
echo !path! | more
And to be safe also with empty variables you could use echo(
echo(!path! | more
There is also a question about pretty print/split the path variable
I suggest to clean up PATH as no directory path in this environment variable should contain ever quotes.
Here is an example batch code for your task:
#echo off
rem Uncomment the line below for testing with C:\PythonXX
rem already in PATH with an erroneous double quote in PATH.
rem set "PATH=C:\PythonXX;"%PATH%"
echo Path before clean up:
echo.
echo PATH=%PATH%
set "PATH=%PATH:"=%"
echo.
echo Path after clean up:
echo.
echo PATH=%PATH%
if /I "%PATH:~0,12%" NEQ "C:\PythonXX;" set "PATH=C:\PythonXX;%PATH%"
echo.
echo Path modified:
echo.
echo PATH=%PATH%
echo.
pause
See the answer on set environment variables with spaces and the other answers referenced there why set "variable=value" should be used even if the value to assign to a variable contains 1 or more quotes itself.

Batch file include external file for variables

I have a batch file and I want to include an external file containing some variables (say configuration variables). Is it possible?
Note: I'm assuming Windows batch files as most people seem to be unaware that there are significant differences and just blindly call everything with grey text on black background DOS. Nevertheless, the first variant should work in DOS as well.
Executable configuration
The easiest way to do this is to just put the variables in a batch file themselves, each with its own set statement:
set var1=value1
set var2=value2
...
and in your main batch:
call config.cmd
Of course, that also enables variables to be created conditionally or depending on aspects of the system, so it's pretty versatile. However, arbitrary code can run there and if there is a syntax error, then your main batch will exit too. In the UNIX world this seems to be fairly common, especially for shells. And if you think about it, autoexec.bat is nothing else.
Key/value pairs
Another way would be some kind of var=value pairs in the configuration file:
var1=value1
var2=value2
...
You can then use the following snippet to load them:
for /f "delims=" %%x in (config.txt) do (set "%%x")
This utilizes a similar trick as before, namely just using set on each line. The quotes are there to escape things like <, >, &, |. However, they will themselves break when quotes are used in the input. Also you always need to be careful when further processing data in variables stored with such characters.
Generally, automatically escaping arbitrary input to cause no headaches or problems in batch files seems pretty impossible to me. At least I didn't find a way to do so yet. Of course, with the first solution you're pushing that responsibility to the one writing the config file.
If the external configuration file is also valid batch file, you can just use:
call externalconfig.bat
inside your script. Try creating following a.bat:
#echo off
call b.bat
echo %MYVAR%
and b.bat:
set MYVAR=test
Running a.bat should generate output:
test
Batch uses the less than and greater than brackets as input and output pipes.
>file.ext
Using only one output bracket like above will overwrite all the information in that file.
>>file.ext
Using the double right bracket will add the next line to the file.
(
echo
echo
)<file.ext
This will execute the parameters based on the lines of the file. In this case, we are using two lines that will be typed using "echo". The left bracket touching the right parenthesis bracket means that the information from that file will be piped into those lines.
I have compiled an example-only read/write file. Below is the file broken down into sections to explain what each part does.
#echo off
echo TEST R/W
set SRU=0
SRU can be anything in this example. We're actually setting it to prevent a crash if you press Enter too fast.
set /p SRU=Skip Save? (y):
if %SRU%==y goto read
set input=1
set input2=2
set /p input=INPUT:
set /p input2=INPUT2:
Now, we need to write the variables to a file.
(echo %input%)> settings.cdb
(echo %input2%)>> settings.cdb
pause
I use .cdb as a short form for "Command Database". You can use any extension.
The next section is to test the code from scratch. We don't want to use the set variables that were run at the beginning of the file, we actually want them to load FROM the settings.cdb we just wrote.
:read
(
set /p input=
set /p input2=
)<settings.cdb
So, we just piped the first two lines of information that you wrote at the beginning of the file (which you have the option to skip setting the lines to check to make sure it's working) to set the variables of input and input2.
echo %input%
echo %input2%
pause
if %input%==1 goto newecho
pause
exit
:newecho
echo If you can see this, good job!
pause
exit
This displays the information that was set while settings.cdb was piped into the parenthesis. As an extra good-job motivator, pressing enter and setting the default values which we set earlier as "1" will return a good job message.
Using the bracket pipes goes both ways, and is much easier than setting the "FOR" stuff. :)
So you just have to do this right?:
#echo off
echo text shizzle
echo.
echo pause^>nul (press enter)
pause>nul
REM writing to file
(
echo XD
echo LOL
)>settings.cdb
cls
REM setting the variables out of the file
(
set /p input=
set /p input2=
)<settings.cdb
cls
REM echo'ing the variables
echo variables:
echo %input%
echo %input2%
pause>nul
if %input%==XD goto newecho
DEL settings.cdb
exit
:newecho
cls
echo If you can see this, good job!
DEL settings.cdb
pause>nul
exit
:: savevars.bat
:: Use $ to prefix any important variable to save it for future runs.
#ECHO OFF
SETLOCAL
REM Load variables
IF EXIST config.txt FOR /F "delims=" %%A IN (config.txt) DO SET "%%A"
REM Change variables
IF NOT DEFINED $RunCount (
SET $RunCount=1
) ELSE SET /A $RunCount+=1
REM Display variables
SET $
REM Save variables
SET $>config.txt
ENDLOCAL
PAUSE
EXIT /B
Output:
$RunCount=1
$RunCount=2
$RunCount=3
The technique outlined above can also be used to share variables among multiple batch files.
Source: http://www.incodesystems.com/products/batchfi1.htm
Kinda old subject but I had same question a few days ago and I came up with another idea (maybe someone will still find it usefull)
For example you can make a config.bat with different subjects (family, size, color, animals) and apply them individually in any order anywhere you want in your batch scripts:
#echo off
rem Empty the variable to be ready for label config_all
set config_all_selected=
rem Go to the label with the parameter you selected
goto :config_%1
REM This next line is just to go to end of file
REM in case that the parameter %1 is not set
goto :end
REM next label is to jump here and get all variables to be set
:config_all
set config_all_selected=1
:config_family
set mother=Mary
set father=John
set sister=Anna
rem This next line is to skip going to end if config_all label was selected as parameter
if not "%config_all_selected%"=="1" goto :end
:config_test
set "test_parameter_all=2nd set: The 'all' parameter WAS used before this echo"
if not "%config_all_selected%"=="1" goto :end
:config_size
set width=20
set height=40
if not "%config_all_selected%"=="1" goto :end
:config_color
set first_color=blue
set second_color=green
if not "%config_all_selected%"=="1" goto :end
:config_animals
set dog=Max
set cat=Miau
if not "%config_all_selected%"=="1" goto :end
:end
After that, you can use it anywhere by calling fully with 'call config.bat all' or calling only parts of it (see example bellow)
The idea in here is that sometimes is more handy when you have the option not to call everything at once. Some variables maybe you don't want to be called yet so you can call them later.
Example test.bat
#echo off
rem This is added just to test the all parameter
set "test_parameter_all=1st set: The 'all' parameter was NOT used before this echo"
call config.bat size
echo My birthday present had a width of %width% and a height of %height%
call config.bat family
call config.bat animals
echo Yesterday %father% and %mother% surprised %sister% with a cat named %cat%
echo Her brother wanted the dog %dog%
rem This shows you if the 'all' parameter was or not used (just for testing)
echo %test_parameter_all%
call config.bat color
echo His lucky color is %first_color% even if %second_color% is also nice.
echo.
pause
Hope it helps the way others help me in here with their answers.
A short version of the above:
config.bat
#echo off
set config_all_selected=
goto :config_%1
goto :end
:config_all
set config_all_selected=1
:config_family
set mother=Mary
set father=John
set daughter=Anna
if not "%config_all_selected%"=="1" goto :end
:config_size
set width=20
set height=40
if not "%config_all_selected%"=="1" goto :end
:end
test.bat
#echo off
call config.bat size
echo My birthday present had a width of %width% and a height of %height%
call config.bat family
echo %father% and %mother% have a daughter named %daughter%
echo.
pause
Good day.
The best option according to me is to have key/value pairs file as it could be read from other scripting languages.
Other thing is I would prefer to have an option for comments in the values file - which can be easy achieved with eol option in for /f command.
Here's the example
values file:
;;;;;; file with example values ;;;;;;;;
;; Will be processed by a .bat file
;; ';' can be used for commenting a line
First_Value=value001
;;Do not let spaces arround the equal sign
;; As this makes the processing much easier
;; and reliable
Second_Value=%First_Value%_test
;;as call set will be used in reading script
;; refering another variables will be possible.
Third_Value=Something
;;; end
Reading script:
#echo off
:::::::::::::::::::::::::::::
set "VALUES_FILE=E:\scripts\example.values"
:::::::::::::::::::::::::::::
FOR /F "usebackq eol=; tokens=* delims=" %%# in (
"%VALUES_FILE%"
) do (
call set "%%#"
)
echo %First_Value% -- %Second_Value% -- %Third_Value%
While trying to use the method with excutable configuration
I noticed that it may work or may NOT work
depending on where in the script is located the call:
call config.cmd
I know it doesn't make any sens, but for me it's a fact.
When "call config.cmd" is located at the top of the
script, it works, but if further in the script it doesn't.
By doesn't work, I mean the variable are not set un the calling script.
Very very strange !!!!

Resources