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.
Related
I am trying to match a batch script to do this tutorial automatically, for any system.
#echo off
for /f "tokens=4* delims= " %%A in ('reg query "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" /v "My Pictures"') do (set loc=%%A %%B)
set loc=%loc%\Spotlight
set /p loc=Location for images (default - "%loc%") [use %%loc%% for default]?:
start robocopy "%localappdata%\Packages\Microsoft.Windows.ContentDeliveryManager_cw5n1h2txyewy\LocalState\Assets" "%temp%\spotlight"
ren %temp%\spotlight\*.* *.jpg
robocopy "%temp%\spotlight" "%loc%"
start %SystemRoot%\explorer.exe %loc%
However, if I try typing %loc%\Spotlight2, it places it in the Desktop\%loc%\spotlight2 (desktop is the current working directory).
Is there a way to make set /p take environment variables?
the usual way to enter a default is an empty input (keeps predefined varable unchanged). But there is a way to enable a variable input. Try the following and answer the question with %windir%
#echo off
setlocal
set x=%username%
set /p "x=enter value ([ENTER] for default: "%x%") "
call set x=%x%
echo %x%
The trick is to expand the variable twice by using another layer of parsing (whith call).
SET /P is basically just an input routine. There is no documented way to have it do further evaluation, and I doubt one exists.
One could try to get CMD to evaluate the contents of a variable to expand any variables in the contents -- I have seem kludges to do this. However, I don't think that is a good way to do what you want.
I think you would be better off using something easier to type, and testing for that explicitly. For example:
set loc=%loc%\Spotlight
echo Enter location for images, or just press [ENTER] to use the default.
echo Default location: %loc%
set userloc=*
set /p userloc=Location?
if not "%userloc%"=="*" set loc=%userloc%
There's a way to do what you want without default locations! It involves variable manipulation:
...
setlocal enabledelayedexpansion
set loc=%loc%\Spotlight
set default=%loc%
set /p "loc=Location [%loc%] for default: "
set out=!loc:%%loc%%=%default%!
echo %out%
...
Hence, typing %loc% replaces it with the original value of loc.
I tested this by typing %loc%\s and it performed as expected. In terms of it being the correct directory, thats to do with how you set the value of loc prior to this code.
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.
(c)ToTheMaker
I found this code here and I'm going to use it but the only problems is I want it to have a user input that will create the folders
For example: "Enter number of folders:"
The value which the user will input will be used as a variable that will create the folders. How am I going to do that?
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET groupsize=10
SET n=1
SET nf=0
FOR %%f IN (*.txt) DO (
IF !n!==1 (
SET /A nf+=1
MD Cake_!nf!
)
MOVE /Y "%%f" Cake_!nf!
IF !n!==!groupsize! (
SET n=1
) ELSE (
SET /A n+=1
)
)
ENDLOCAL
PAUSE
Executing in a command prompt window either help set or set /? results in printing several help pages into the console window for command SET which should be read carefully. The help explains also set /P for prompting user for a value or string.
#echo off
set "FolderCount=1"
set /P "FolderCount=Enter number of folders (default: %FolderCount%): "
for /L %%N in (1,1,%FolderCount%) do md "Folder%%N"
set "FolderCount="
This little batch code defines the environment variable FolderCount with value 1 as default value which is used when the user hits just key RETURN or ENTER on prompt.
The user is asked next for the number of folders to create. The string entered by the user is assigned to environment variable FolderCount. The user hopefully enters a positive number and not something different.
The FOR loop creates the folders in current directory with name Folder and the current number appended which is automatically incremented by 1 on each loop run starting with value 1.
The last line deletes the environment variable FolderCount not needed anymore.
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.
echo /?
for /?
md /?
set /?
Edit: The final batch code for the entire task with moving the text files, too.
#echo off
rem Delayed expansion required for variable FolderIndex in FOR loop.
rem Command setlocal additionally creates a new environment variable
rem table with copying all existing variables to the new table.
setlocal EnableDelayedExpansion
rem Ask the batch user for number of subfolders to create and create them.
set "FolderCount=1"
set /P "FolderCount=Enter number of folders (default: %FolderCount%): "
for /L %%N in (1,1,%FolderCount%) do md "Folder%%N"
rem Move all *.txt files from current folder into the created subfolders.
set "FolderIndex=0"
for %%F in (*.txt) do (
set /A FolderIndex+=1
move /Y "%%~F" "Folder!FolderIndex!\%%~nxF"
if !FolderIndex! == %FolderCount% set "FolderIndex=0"
)
rem Restore previous environment which results in destroying
rem current environment table with FolderIndex and FolderCount.
endlocal
I'm an amateur on the usage of the FOR command. I need a batch file that will run one of 5 file conversion tools based on a file's extension. I want to drop a file onto the batch file icon and have it converted.
Since my list is huge, I can't use nested IF's.
What I've tried so far:
#ECHO OFF
SET cadfile=.dwg .dxf .dwf
SET gsfile=.ps .eps .epi .epsp
SET xxxxxx=.xx .xx and goes on
FOR %%~x1 in (%cadfile%) do (
Do some action
FOR %%~x1 in (%gsfile%) do (
Do some other action
)
)
The %%~x1 variable is used for file extension of file, which dragged and dropped over the batch file.
(edited to make more clear)
FOR %%a in (%cadfile%) do (
if /i "%~x1"=="%%a" some_action "%~1"
)
... and follow the bouncing ball for the rest of the utilities/lists
I think this will work for you. It looks through all your groups of extensions in a single For loop and when the matching extension is found, calls a label where you can do the conversion and any related tasks. You'll need to finish the "groupN" variables and labels.
#echo off
SETLOCAL EnableDelayedExpansion
set file="%1"
set ext=%~x1
:: Set the 5 groups of extensions that have different converters
set group1=.dwg, .dxf, .dwf
set group2=.ps, .eps, .epi, .epsp
For %%A in (1 2 3 4 5) do (
set groupnum=group%%A
call set thisgroup=%%!groupnum!%%
:: Look for extension in this group
echo.!thisgroup!|findstr /i /C:"%ext%" >nul 2>&1
if not errorlevel 1 call :group%%A
:: else go loop next group
)
echo Extension not found in any group &pause &goto end
:group1
echo group1 file to convert is %file%
goto end
:group2
echo group2 file to convert is %file%
goto end
:end
pause
exit
The following method allows you to easily add and modify your list of extensions/applications. Please note that you just need to edit the values placed inside the first FOR command; the rest of the program is the solution you don't need to care of...
#echo off
setlocal EnableDelayedExpansion
rem Define the list of extensions per application:
rem (this is the only part that you must edit)
for %%a in ("cadfile=.dwg .dxf .dwf"
"gsfile=.ps .eps .epi .epsp"
"xxxxxx=.xx .xx1 .xx2") do (
rem The rest of the code is commented just to be clear,
rem but you may omit the reading of this part if you wish
rem Separate application from its extensions
rem and create a vector called "ext" with an element for each pair
for /F "tokens=1,2 delims==" %%b in (%%a) do (
rem For example: %%b=cadfile, %%c=.dwg .dxf .dwf
for %%d in (%%c) do set "ext[%%d]=%%b"
rem For example: set "ext[.dwg]=cadfile", set "ext[.dxf]=cadfile", set "ext[.dwf]=cadfile"
rem In the next line: set "ext[.ps]=gsfile", set "ext[.eps]=gsfile", etc...
)
)
rem Now process the extension of the file given in the parameter:
if defined ext[%~x1] goto !ext[%~x1]!
echo There is no registered conversion tool for %~x1 extension
goto :EOF
:cadfile
echo Execute cadfile on %1 file
rem cadfile %1
goto :EOF
:gsfile
echo Execute gsfile on %1 file
rem gsfile %1
goto :EOF
etc...
If each conversion tool is executed in the same way and don't require additional parameters (just the filename), then you may omit the individual sections and directly execute the conversion tools this way:
if defined ext[%~x1] !ext[%~x1]! %1
For further explanations on array concept, see this post.
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 !!!!