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 !!!!
Related
Basically I want to make it so that when the player within my games makes a name, it saves the name variable into a text file. Then within the txt file, upon the loading the save file, the game will look at the text file labled "name.txt" for instance, and inside will contain the player name upon which will be extracted by some means so the new save can be loaded, but for other player variables such as wealth or location, those are dynamic variables and may need to be changed often, so what I was suggesting is that how would I constantly update this "weath.txt" file and/or "location.txt" file for instance and overwrite the previous text lines containing there previous location. As for how I would set the variable of any given player characteristic such as its name, I have used this patch of code to no avail:
#echo off
:start
set /p name=">"
(#echo off %name%)>"C:\Users\%username%\Desktop\gamedir\name.txt"
SET name=
for /f "delims=" %%i in (name.txt) do call :setname %%i
echo final name = %name%
goto exit
:Setname
Set name=%name% %1%
goto :eof
:exit
pause
rem showing result which appears to do nothing
echo %name%
As for extracting this variable upon a load game, I have absolutely no idea how to do that at all. Would appreciate any help!
A simple enough way to handle saving and loading is to store game data in a bat file, then call it when it's time to load.
The below subroutine example prefixes any variable to be saved with Save. so that it can be output using the set command within a for loop, to ensure only the desired variables are saved.
:Save
REM refresh the save file
>"%name%.bat" Echo #Echo off
REM output all requied values prefixed with Save. using the Set Command to allow easy loading
For /F "UsebackQ Tokens=* Delims=" %%S in (`"Set Save."`) Do >>"%name%.bat" Echo Set "%%~S"
REM Finalise the save with a return to the calling script
>>"%name%.bat" Echo Goto :EOF
Exit /B
:Load
REM Calls the save file which contains commands to set all relevant variables
CALL "%name%.bat"
Exit /B
You can easily support multiple characters by storing them in their own folder and using DIR to display them and an input method to select them.
`DIR "%TEMP%\GameName\characters" /B /P`
I am writing a CMD script to generate documentation markdown pages for my GitHub repository. I have decided to give the script a default directory for the project and its documentation folder, and if the end user wants to use a different one, they must specify it, before the next step.
My code is something like:
echo.
setlocal enabledelayedexpansion
set projectDirectory=GroupManagementAppServer
set documentationFolder=documentation
rem ask user for confirmation of projectDirectory,documentationFolder to use
choice /m "By default, project directory is %projectDirectory% and documentation is stored in %documentationFolder%. Should I use these?"
rem if no
if %errorlevel% == 2 (
rem get projectDirectory,documentationFolder from user
set /p relativeDocumentationPathname=Please enter relative pathname to the documentation folder:
rem parse input
call :getAbsolutePath %relativeDocumentationPathname%
set documentationFolder=%_absolutePath%
set projectDirectory="%documentationFolder%\.."
)
echo %_absolutePath%
echo %documentationFolder%
echo %projectDirectory%
:getAbsolutePath
SETLOCAL
for %%i in ("%1%") do (
set filedrive=%%~di
set filepath=%%~pi
set filename=%%~ni
set fileextension=%%~xi
)
ENDLOCAL & SET _absolutePath=%filedrive%%filepath%%filename%%fileextension%
thus far, and when the echos complete, it's as if documentationFolder was never redefined! What the heck is going on, and how do I fix this, so that I can implement the rest of this and move on to actually getting some documentation on?
Here is the fixed code with delayed expansion properly applied, the sub-routine reduced and some minor improvements, mainly related to quotation:
echo/
setlocal EnableDelayedExpansion
set "projectDirectory=GroupManagementAppServer"
set "documentationFolder=documentation"
rem // Ask user for confirmation of `projectDirectory`, `documentationFolder` to use:
choice /M "By default, project directory is '%projectDirectory%' and documentation is stored in '%documentationFolder%'. Should I use these?"
rem // If no:
if %errorlevel% == 2 (
rem // Get `projectDirectory`, `documentationFolder` from user:
set /P relativeDocumentationPathname="Please enter relative pathname to the documentation folder: "
rem // Parse input:
call :getAbsolutePath "%relativeDocumentationPathname%"
set "documentationFolder=!_absolutePath!"
set "projectDirectory=!documentationFolder!\.."
)
echo %_absolutePath%
echo %documentationFolder%
echo %projectDirectory%
goto :EOF
:getAbsolutePath
setlocal
for /D %%I in ("%~1") do (
set "filespec=%%~fI"
)
endlocal & set "_absolutePath=%filespec%"
I'd suggest you use the SO search facility in the top black bar and try to find delayedexpansion. There are hundreds of items on this matter.
Fundamentally, when a block (parenthesised series of statements) is encountered, the entire block is evaluated, substituting the then-current values of the variables and once that is done, the code is executed.
In your case, call echo %%var%% would show the modified values, or using the modified values within a subroutine (like call :arouthethatechosthevalues) would implement the new values.
I am trying to get this script to jump to another section of the script if there is no input from the user.
Down at the if %input%== area.
What I'm trying to do is skip to the section where the script checks for .mp4 files and moves them if they are there. Am I supposed to set a variable or loop for that section? Thanks for any replies
#echo off
echo Checking for youtube-dl updates.
pause
youtube-dl -U
rem Enter the url or urls that you want to download from
set /p input="Enter the url(s) you want to download:"
rem Uses the youtube-dl continue (-c) option to download multiple files if not in a playlist
youtube-dl -c "%input%"
rem pause
if %input%=="" GOTO:EOF
cls
echo Download complete, please wait while files are transfered to appropiate folder
pause
for %%o in (.mp4) do move "*%%o" "E:\Documents\scripts\videos\"
if not exist do echo .mp4 files are no longer in this directory
pause
How about following script? This script waits for 3 seconds while "file.mp4" doesn't exist. It keeps to wait until the file exists. About "filename", you can change for your script.
#echo off
set waittime=3
set filename=file.mp4
:loop
if not exist %filename% (
Timeout /t %waittime% /nobreak > nul
goto loop
)
echo Find %filename%
When doing string comparison in batch you have to make sure, that both parts are equal, which in your case will never happen! In most languages strings have double quotes around them. In batch they usually do not.
To solve your problem enclose %input% in double quotes as well.
Note that it can be useful to do something like "x%input%"=="x" to prevent certain characters like <>|to be at the beginning of the comparison string.
You can check this on your own with these few lines:
#echo off
set /p input="Input something or nothing here "
echo %input%
echo "%input%"
pause
If you are hitting Return without any input you will see that only the bottom one will output "" which is the string you are comparing to.
How do I write a batch file which gets an input variable and sends it to another batch file to be processed.
Batch 1
I don't know how to send a variable to batch 2 which is my problem here.
Batch 2
if %variable%==1 goto Example
goto :EOF
:Example
echo YaY
You don't need to do anything at all. Variables set in a batch file are visible in a batch file that it calls.
Example
test1.bat
#echo off
set x=7
call test2.bat
set x=3
call test2.bat
pause
test2.bat
echo In test2.bat with x = %x%.
Output
... when test1.bat runs.
In test2.bat with x = 7.
In test2.bat with x = 3.
Press any key to continue . . .
You can pass in the batch1.bat variables as arguments to batch2.bat.
arg_batch1.bat
#echo off
cls
set file_var1=world
set file_var2=%computername%
call arg_batch2.bat %file_var1% %file_var2%
:: Note that after batch2.bat runs, the flow returns here, but since there's
:: nothing left to run, the code ends, giving the appearance of ending with
:: batch2.bat being the end of the code.
arg_batch2.bat
#echo off
:: There should really be error checking here to ensure a
:: valid string is passed, but this is just an example.
set arg1=%~1
set arg2=%~2
echo Hello, %arg1%! My name is %arg2%.
If you need to run the scripts simultaneously, you can use a temporary file.
file_batch1.bat
#echo off
set var=world
:: Store the variable name and value in the form var=value
:: > will overwrite any existing data in args.txt, use >> to add to the end
echo var1=world>args.txt
echo var2=%COMPUTERNAME%>>args.txt
call file_batch2.bat
file_batch2.bat
#echo off
cls
:: Get the variable value from args.txt
:: Again, there is ideally some error checking here, but this is an example
:: Set no delimiters so that the entire line is processed at once
for /f "delims=" %%A in (args.txt) do (
set %%A
)
echo Hello, %var1%! My name is %var2%.
If you are reading the answers and still getting problems, you may be using setlocal wrong.
setlocal works like namespaces with endlocal closing the last opened namespace.
If you put endlocal at the end of your callee as I used to do by default, your variable will be lost in the previously opened setlocal.
As long as your variable is set in the same "local" as your caller file's you will be ok.
Here are 2 ways. One writes to a file while the other does not.
All include: setlocal EnableDelayedExpansion
Batch1: (.bat or .cmd)
set myvar1=x 1920
set myvar2=y 1080
set myvar > MyConfig.ini
REM (Use >> to append file and preserve existing entries instead of overwriting.)
MyConfig.ini will be created/appended containing:
myvar1=x 1920
myvar2=y 1080
Batch2:
for /f "delims== tokens=1,2" %%I in (myconfig.ini) do set %%I=%%J
Then, myvar1 and myvar2 will be set to their stored values.
To avoid any disk file storage, try something I cooked up:
Batch1:
(may contain:) start /b cmd /c Batch2.bat
title set myvar1=x 1920^& set myvar2=y 1080
Batch2:
for /f "tokens=*" %%G in ('gettitle.exe') do (set titlenow=%%G)
%titlenow%
The result is that Batch2 runs the commands...
set myvar1=x 1920& set myvar2=y 1080
...to replicate the variables from Batch1.
The vars might then be changed and returned to Batch1 in the same manner.
It's ideal for parallel processing AI apps on supercomputers running Windows 10! lol
gettitle.exe is from http://www.robvanderwoude.com
You can find out the solution from below-
variable goes here >> "second file goes here.bat"
What this code does is that it writes the variables to the second file if existing.Even if it does not exist, it will create a new file.
I'm creating a batch file that will call a few other batch files. One of the batch files to be called has a "CLS" in it. Is there some way I can get the wrapping batch file to ignore the child "CLS" call while still letting the CLS work when the child batch file is called directly from the command line?
Here is a real ugly hack :-)
The CLS screen command does not work if stdout is redirected to con: - it prints the Form Feed character (0x0C) to the screen. On my machine it looks like the female symbol.
#echo off
:: Normal call, CLS in called batch clears the screen
call batch2
:: Redirected call, CLS in called batch prints <FF> character to screen (without newline)
call batch2 >con:
Advantage - No need to modify your child script.
Disadvantage - The unwanted <FF> character appears in the output.
Hmm - quite how long IS a piece of string?
Generally, you could try this in your CALLed batch:
#echo off
setlocal
set skipcls=%1
if defined skipcls (if "%skipcls%"=="::" (shift) else (set "skipcls="))
...
%SKIPCLS% cls
...
Then call your batch with an extra first parameter ::
If the first parameter is :: it will be shifted out and skipcls will be set to ::; otherwise skipcls will be empty, hence the %skipcls% CLS line will be skipped or executed...
Amendment:
Perhaps
#echo off
setlocal
if "%~1"=="::" (shift&set "skipcls=REM ") else (set "skipcls=")
...
%SKIPCLS% cls
...
would be simpler. Setting skipclsto a value of REMSPACE rather than :: would allow the technique to be used more reliably within parenthesised constructs.
Of course, another approach would be to set skipcls appropriately in the caller routine.
OR set skipcls to any non-empty value you like and use
if [not] defined skipcls ...
which supports constructs like if it's set do this, if not do that.
And of course, it's not restricted to CLS, so perhaps a different variable name should be used.
And if you're really feeling adventurous, you could set redirect to >>"a_filename" in the caller and then have
%redirect% ECHO whatever
to steer output to an externally-controled destination.
Seems to be many ways to apply a simple concept.
You could create a temp batch file, where all cls commands have been ingnored:
setlocal enabledelayedexpansion
set target=targetname.bat
for /f "usebackq delims=" %%a in ("%target%") do (
set line=%%a
Echo !line:cls=Rem Cls! >> temp_%target%
Echo !line:CLS=Rem Cls! >> temp_%target%
Echo !line:Cls=Rem Cls! >> temp_%target%
Rem Include CLs ClS cLS, etc. if the person who wrote the file is not case consistent
Rem : (
set line=
)
call temp_%target%
set target=
Endlocal
And that should do what you want perfectly.
Mona