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.
Firstly I would like to say that it has been 10 years since I dabbled in programming and my memory of what I have learnt seems to be failing me. I hope I have included enough info for people to see what I am trying to do without going to extremes. There may be better ways to do what I have done and any suggestions will be appreciated.
I have 2 batch files, both in the parent directory, 1 to back up files in 2 sub directories of the parent directory to another sub directory a few levels down from the parent directory.
eg.
The batch files are in the directory "Test Folder"
The files to be backed up are in the location :-
Test Folder\LocalProfiles
and
Test Folder\SavedLocal
The folder where they are to be backed up to is called "Backup" with created directories using a name chosen by the users eg. "Blue" and then in another directory under that using the current Date and Time as the name of the directory, followed by the original directory. (The format of the date and time, currently 2016-04-06 23.03.15 has not been finalised as the space that I am using in the backup batch file between the date and time has been giving me problems in this batch file when restoring). So the location of the files for this user that have been backed up would be:-
Test Folder\Backup\Blue\2016-04-06 23.03.15\LocalProfiles
and
Test Folder\Backup\Blue\2016-04-06 23.03.15\SavedLocal
I have written a batch file that lists all of the user chosen directories
1st Screen
then after the user chooses a directory they then choose a directory from the directories labeled with a date and time
2nd Screen
and then it copies the files below back to the original location.
The problem with this is that it takes time and can lead to typos, especially when putting in the date and time. I wanted to make it as fool proof as possible by creating a menu where the user can select a number corresponding with a user created directory.
The end result would be:-
1st menu screen
Select one of the following
1. Blue
2. Red
3. Green
etc.
and then display the directories with dates and times that are contained in the selected directory on the next menu screen.
2nd Menu
Select one of the following
1. 2016-04-05 21.03.28
2. 2016-04-05 21.05.51
3. 2016-04-06 23.00.14
etc.
The user would simply type the number corresponding to the selected directory and press enter.
In the end I would like to merge both batch files with a choice to backup or restore but I need this to work properly first.
The following is the code for restoring the files, without the use of menus. It works but it doesn't always handle spaces in directory names and I'm sure there would be a better way to handle the error checking and messages.
#echo off
if not defined in_subprocess (cmd /k set in_subprocess=y ^& %0 %*) & exit )
:Prompt for Profile Name
cls
echo.
echo Please select one of the following Profiles to load.
echo.
:: Create the list of Profiles from the Directory Names
for /d %%D in (.\Backup\*) do echo %%~nxD
echo.
:: Prompt for Profile Name for multiple Profile Names
set "profile="
set /p profile=Please enter a Profile Name.
:: OR
:: Consider adding Fixed Profile Name to top of list/menu as the default for quick saving of files.
:: set "profile=Default"
:: Check that the Profile exists
echo.
if not exist ".\Backup\%profile%" echo The Profile %profile% does not exist.
if not exist ".\Backup\%profile%" pause
if [%profile%] == [] echo A Profile must have a name.
if [%profile%] == [] pause
:: If input is empty or doesn't exist return to the beginning.
if not exist ".\Backup\%profile%" goto Prompt for Profile Name
if [%profile%] == [] goto Prompt for Profile Name
:Saved
:: Create the list of Saved locations from the Directory Names
cls
echo.
echo Existing Saved Locations for the Profile named %profile%
echo.
for /d %%S in (".\Backup\%profile%\*") do echo %%~nxS
:: Prompt for Saved location for selected Profile Name
set "save="
echo.
set /p save=Please enter a Saved location.
:: Check that the Saved Location exists
echo.
if not exist ".\Backup\%profile%\%save%" echo The Saved Location %profile%\"%save%" does not exist.
if not exist ".\Backup\%profile%\%save%" echo Select a Saved Location from the list.
if not exist ".\Backup\%profile%\%save%" pause
:: Check that the Saved Location is not empty
if [%save%] == [] echo Select a Saved Location from the list.
if [%save%] == [] pause
:: If input is empty or doesn't exist return to the beginning.
if not exist ".\Backup\%profile%\%save%" goto Saved
if [%save%] == [] goto Saved
:: Check for Saved files
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" echo.
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" echo LocalData.profile for %profile% does not exist at location "%save%\LocalProfiles\"
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" echo.
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" echo LocalMember.profile for %profile% does not exist at "%save%\SavedLocal\"
:: If LocalData.profile doesn't exist display message and return to Saves.
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" echo.
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" echo The Save at %save% is incomplete or does not exist.
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" echo.
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" echo Please select another Save.
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" Pause
if not exist ".\Backup\%profile%\%save%\LocalProfiles\LocalData.profile" goto Saved
:: If LocalMember.profile doesn't exist display message and return to Saves.
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" echo.
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" echo The Save at %save% is incomplete or does not exist.
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" echo.
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" echo Please select another Save.
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" Pause
if not exist ".\Backup\%profile%\%save%\SavedLocal\LocalMember.profile" goto Saved
:Load Save
:: Restore the valid Save to the original location.
echo.
echo Restoring Saved Files
robocopy ".\Backup\%profile%\%Save%\LocalProfiles" ".\LocalProfiles"
robocopy ".\Backup\%profile%\%Save%\SavedLocal" ".\SavedLocal"
echo.
echo Files Restored
pause
exit
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "directories=localprofiles savedlocal"
CALL :zap$
:: First menu
CALL :choose blue default green red "white and puce stripes" /now
SET "userchosenname=%$choice%"
:: Don't know what format you are using for date and time.
:: I use dd/mm/yyyy and hh:mm:ss.hh
SET "backupdatestamp=%date:~-4%-%date:~3,2%-%date:~0,2% %time::=.%"
SET "backupdatestamp=%backupdatestamp:~0,-3%"
CALL :choose "backup as %backupdatestamp%"
FOR /f "delims=" %%a IN ('dir /b /ad "%sourcedir%\%userchosenname%\" 2^>nul') DO (
IF EXIST "%sourcedir%\%userchosenname%\%%a\localprofiles\." CALL :choose "restore from %%a"
)
CALL :choose /now "With this prompt"
FOR %%a IN (%directories%) DO (
IF "%$choice:~0,6%"=="backup" (
ECHO(MD "%sourcedir%\%userchosenname%\%backupdatestamp%\%%a"
ECHO(XCOPY /s/e "%sourcedir%\%%a" "%sourcedir%\%userchosenname%\%backupdatestamp%\%%a\"
) ELSE (
ECHO(XCOPY /s/e "%sourcedir%\%userchosenname%\%$choice:~13%\%%a\*" "%sourcedir%\%%a\"
)
)
GOTO :EOF
:: remove variables starting $
:ZAP$
FOR /F "delims==" %%Z In ('set $ 2^>Nul') DO SET "%%Z="
GOTO :EOF
:: Make a choice
:: If a parameter is '/now' actually make a choice with prompt next-parameter
:: else accumulate choices
:choose
SET "$item=%~1"
IF NOT DEFINED $item GOTO :EOF
IF /i "%$item%"=="/now" GOTO makechoice
IF NOT DEFINED $options cls
IF NOT DEFINED $choices SET "$choices=123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
SET "$options=%$options%%$choices:~0,1%"
SET "$choices=%$choices:~1%"
SET /a $optioncount+=1
SET "$option%$optioncount%=%~1"
ECHO(%$options:~-1% %~1
SHIFT
GOTO choose
:makechoice
IF "%~2" neq "" SET "$options=%$options% /m "%~2""
choice /c %$options%
CALL SET "$choice=%%$option%errorlevel%%%"
CALL :zap$&SET "$choice=%$choice%"
GOTO :EOF
You would need to change the setting of sourcedir to suit your circumstances.
The required MD commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(MD to MD to actually create the directories. Append 2>nul to suppress error messages (eg. when the directory already exists)
The required XCOPY commands are merely ECHOed for testing purposes. After you've verified that the commands are correct, change ECHO(XCOPY to XCOPY to actually copy the files. Append >nul to suppress report messages (eg. 1 file copied)
The basis of this routine is the :choose subroutine which accumulates options into $-variables. It can be run either with 1 or more parameters, optionally quoted (in case a parameter includes a space.)
If the parameter /now is found, the routine asks for a choice to be made from the options presented. If the /now parameter is followed by another, that following parameter is used as a prompt for choice.
The CALL :zap$&SET "$choice=%$choice%" line uses the same parsing trick used to export values from a setlocal/endlocal bracket
Beyond that, all you'd need to do is fix the setting of backupdatestamp using the batch substringing facilities explained in the documentation
set /?
from the prompt.
Okay so I am currently creating a Artificial Intelligence program (VERY basic),
and I need to be able to log the name inputted by the user. I don't understand from other articals posted.
Here's the area of code:
set /p input= Before we talk, I'd like to ask your name so I can properly address you. Please print your name.
set /p Name=
As you can see here, we have an input area and I need the file to save the inputted text, and re-open the file and use the name, every time you go onto this program.
Thanks in advance for any help!
#echo off
setlocal EnableDelayedExpansion
call :GetName
if not defined Name (
echo Before we talk, I'd like to ask your name. Please print your name.
set /p Name=
echo set "Name=!Name!" >> "%~F0"
) else (
echo Hello %Name%, I am glad to see you again.
)
rem The rest of the code goes here...
goto :EOF
rem Be sure that the next is the last line in this file:
:GetName
set /p Input=Enter name?
echo set Input=%input% > "c:\somefolder\somefile.bat"
to use
if not defined %input% call "c:\somefolder\somefile.bat"
See if /?, echo /?, and set /?.
I am trying to use a BATCH file as a choose your own adventure story in which the user can create a character and be called by name and such by the characters in the story. It hit me an hour back that it would be cool if as or after the BATCH file containing the story was run it made a log of all of the ECHO commands so that later the player could read what they did on any given play threw.
I would like the log file to read like this:
%date% %time%
(all text displayed by echo commands for the length the file runs)
Unfortunately all I can figure out how to do is to make a loge file with just the date and time. Putting ">> StoryLog.txt" works to make the .txt file and I get the date and time in there but it just displays the text ">> StoryLog.txt" after what I want the batch file to display in echoed txt as in "You go north down the path >> StoryLog.txt" is shown on the screen. This naturally just wont work. What do I do?
for this purpose I use the following:
set LogFile=somepath\logfile.txt
set logg=^> _^&type _^&type _^>^>%LogFile%
echo this goes to screen AND file! %logg%
This is a bit tricky.
So let's disassemble that line to four parts:
set logg= ^> _ ^&type _ ^&type _^>^>%LogFile%
The Idea is to print the line to a temporary file (named _) (second part)
then type the contents of that file to screen (third part)
then type it to the logfile (fourth part).
Put that all to a variable (first part), so you don't have to type that monsterstring to every line. (this is the reason why the > and & are escaped with ^)
So every time you use
echo whatever %logg%
it will appear on the screen AND write to %logfile%
Note that this will also work:
%logg% echo whatever
Edit djangofan:
Also, you can do it with functions:
#ECHO off
:: do not enable delayed expansion since it will break this method
SETLOCAL ENABLEEXTENSIONS
SET LogFile=logfile.out
SET Logg=^> tmp.out^&^& type tmp.out^&^&type tmp.out^>^>%LogFile%
CALL :logit "This is my message!"
CALL :logit "Hear my thunder?"
GOTO :end
:logit
ECHO %~1 %Logg%
DEL /Q tmp.out
EXIT /B 0
:end
pause
Edit Stephan:
If you use CALL, the %logg% would be overkill. In that case I would just use:
:logit
echo %~1
echo %date%,%time% - %~1 >>logfile
exit /b 0
This might be the best solution to the original question, because the Date/Time will be written into logfile, but not on the screen.
Btw: you don't have to delete the tempfile every time you use it, just delete it one time, just before the batch ends.
As my other answer got quite long due to some edits, here is a new suggestion
(not much changed, but it makes the code quite easy to read):
#ECHO off
SET LogFile=logfile.out
set "say=call :logit "
%say% "This is my message!"
%say% "Hear my thunder?"
GOTO :end
:logit
ECHO %~1
echo %date% %time% - %~1 >>%logfile%
EXIT /B 0
:end
You cannot do that in batch script literally, but if you define functions, you can output data within the function block that also writes to a log file before the function returns, like so:
#ECHO off
SETLOCAL ENABLEDELAYEDEXPANSION ENABLEEXTENSIONS
echo. 2>outfile.log
CALL :functionPerson "Jon Doe" jump
GOTO :END
:functionPerson fullname action
ECHO fullname is ^"%~1^"
ECHO action is %2
ECHO %1 performs action %2>> outfile.log
EXIT /B 0
:END
pause
Each time you echo for the player to know what happens, you could also echo into your log file, adding date and time at the beginning of your line :) Simple as that for me.
Don't know how your script looks though.
Locked. This question and its answers are locked because the question is off-topic but has historical significance. It is not currently accepting new answers or interactions.
What are some of the lesser know, but important and useful features of Windows batch files?
Guidelines:
One feature per answer
Give both a short description of the feature and an example, not just a link to documentation
Limit answers to native funtionality, i.e., does not require additional software, like the Windows Resource Kit
Clarification: We refer here to scripts that are processed by cmd.exe, which is the default on WinNT variants.
(See also: Windows batch files: .bat vs .cmd?)
Line continuation:
call C:\WINDOWS\system32\ntbackup.exe ^
backup ^
/V:yes ^
/R:no ^
/RS:no ^
/HC:off ^
/M normal ^
/L:s ^
#daily.bks ^
/F daily.bkf
PUSHD path
Takes you to the directory specified by path.
POPD
Takes you back to the directory you "pushed" from.
Not sure how useful this would be in a batch file, but it's a very convenient command to use in the command prompt:
C:\some_directory> start .
This will open up Windows Explorer in the "some_directory" folder.
I have found this a great time-saver.
I have always found it difficult to read comments that are marked by a keyword on each line:
REM blah blah blah
Easier to read:
:: blah blah blah
Variable substrings:
> set str=0123456789
> echo %str:~0,5%
01234
> echo %str:~-5,5%
56789
> echo %str:~3,-3%
3456
The FOR command! While I hate writing batch files, I'm thankful for it.
FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do #echo %i %j %k
would parse each line in myfile.txt, ignoring lines that begin with a semicolon, passing the 2nd and 3rd token from each line to the for body, with tokens delimited by commas and/or spaces.
Notice the for body statements reference %i to get the 2nd token, %j to get the 3rd token, and %k to get all remaining tokens after the 3rd.
You can also use this to iterate over directories, directory contents, etc...
Rather than litter a script with REM or :: lines, I do the following at the top of each script:
#echo OFF
goto :START
Description of the script.
Usage:
myscript -parm1|parm2 > result.txt
:START
Note how you can use the pipe and redirection characters without escaping them.
The path (with drive) where the script is : ~dp0
set BAT_HOME=%~dp0
echo %BAT_HOME%
cd %BAT_HOME%
The %~dp0 piece was mentioned already, but there is actually more to it:
the character(s) after the ~ define the information that is extracted.
No letter result in the return of the patch file name
d - returns the drive letter
p - returns the path
s - returns the short path
x - returns the file extension
So if you execute the script test.bat below from the c:\Temp\long dir name\ folder,
#echo off
echo %0
echo %~d0
echo %~p0
echo %~dp0
echo %~x0
echo %~s0
echo %~sp0
you get the following output
test
c:
\Temp\long dir name\
c:\Temp\long dir name\
.bat
c:\Temp\LONGDI~1\test.bat
\Temp\LONGDI~1\
And if a parameter is passed into your script as in
test c:\temp\mysrc\test.cpp
the same manipulations can be done with the %1 variable.
But the result of the expansion of %0 depends on the location!
At the "top level" of the batch it expands to the current batch filename.
In a function (call), it expands to the function name.
#echo off
echo %0
call :test
goto :eof
:test
echo %0
echo %~0
echo %~n0
The output is (the batchfile is started with myBatch.bat )
myBatch.bat
:test
:test
myBatch
By using CALL, EXIT /B, SETLOCAL & ENDLOCAL you can implement subroutines with local variables.
example:
#echo off
set x=xxxxx
call :sub 10
echo %x%
exit /b
:sub
setlocal
set /a x=%1 + 1
echo %x%
endlocal
exit /b
This will print
11
xxxxx
even though :sub modifies x.
Sneaky trick to wait N seconds (not part of cmd.exe but isn't extra software since it comes with Windows), see the ping line. You need N+1 pings since the first ping goes out without a delay.
echo %time%
call :waitfor 5
echo %time%
goto :eof
:waitfor
setlocal
set /a "t = %1 + 1"
>nul ping 127.0.0.1 -n %t%
endlocal
goto :eof
Escaping the "plumbing":
echo ^| ^< ^> ^& ^\ ^^
Being able to run commands and process the output (like backticks of '$()' in bash).
for /f %i in ('dir /on /b *.jpg') do echo --^> %i
If there are spaces in filenames, use this:
for /f "tokens=*" %i in ('dir /on /b *.jpg') do echo --^> %i
Creating an empty file:
> copy nul filename.ext
To hide all output from a command redirect to >nul 2>&1.
For example, the some command line programs display output even if you redirect to >nul. But, if you redirect the output like the line below, all the output will be suppressed.
PSKILL NOTEPAD >nul 2>&1
EDIT: See Ignoring the output of a command for an explanation of how this works.
PAUSE
Stops execution and displays the following prompt:
Press any key to continue . . .
Useful if you want to run a batch by double-clicking it in Windows Explorer and want to actually see the output rather than just a flash of the command window.
The equivalent of the bash (and other shells)
echo -n Hello # or
echo Hello\\c
which outputs "Hello" without a trailing newline. A cmd hack to do this:
<nul set /p any-variable-name=Hello
set /p is a way to prompt the user for input. It emits the given string and then waits, (on the same line, i.e., no CRLF), for the user to type a response.
<nul simply pipes an empty response to the set /p command, so the net result is the emitted prompt string. (The variable used remains unchanged due to the empty reponse.)
Problems are: It's not possible to output a leading equal sign, and on Vista leading whitespace characters are removed, but not on XP.
Search and replace when setting environment variables:
> #set fname=%date:/=%
...removes the "/" from a date for use in timestamped file names.
and substrings too...
> #set dayofweek=%fname:~0,3%
Integer arithmetic:
> SET /A result=10/3 + 1
4
Command separators:
cls & dir
copy a b && echo Success
copy a b || echo Failure
At the 2nd line, the command after && only runs if the first command is successful.
At the 3rd line, the command after || only runs if the first command failed.
Output a blank line:
echo.
You can chain if statements to get an effect like a short-circuiting boolean `and'.
if foo if bar baz
To quickly convert an Unicode text file (16bit/char) to a ASCII DOS file (8bit/char).
C:\> type unicodeencoded.txt > dosencoded.txt
as a bonus, if possible, characters are correctly mapped.
if block structure:
if "%VS90COMNTOOLS%"=="" (
echo: Visual Studio 2008 is not installed
exit /b
)
Delayed expansion of variables (with substrings thrown in for good measure):
#echo off
setlocal enableextensions enabledelayedexpansion
set full=/u01/users/pax
:loop1
if not "!full:~-1!" == "/" (
set full2=!full:~-1!!full2!
set full=!full:~,-1!
goto :loop1
)
echo !full!
endlocal
Doesn't provide much functionality, but you can use the title command for a couple of uses, like providing status on a long script in the task bar, or just to enhance user feedback.
#title Searching for ...
:: processing search
#title preparing search results
:: data processing
Don't have an editor handy and need to create a batch file?
copy con test.bat
Just type away the commands, press enter for a new line.
Press Ctrl-Z and Enter to close the file.
example of string subtraction on date and time to get file named "YYYY-MM-DD HH:MM:SS.txt"
echo test > "%date:~0,4%-%date:~5,2%-%date:~8,2% %time:~0,2%_%time:~3,2%_%time:~6,2%.txt"
I use color to indicate if my script end up successfully, failed, or need some input by changing color of text and background. It really helps when you have some machine in reach of your view but quite far away
color XY
where X and Y is hex value from 0 to F, where X - background, Y - text, when X = Y color will not change.
color Z
changes text color to 'Z' and sets black background, 'color 0' won't work
for names of colors call
color ?
Total control over output with spacing and escape characters.:
echo. ^<resourceDir^>/%basedir%/resources^</resourceDir^>
TheSoftwareJedi already mentioned the for command, but I'm going to mention it again as it is very powerful.
The following outputs the current date in the format YYYYMMDD, I use this when generating directories for backups.
for /f "tokens=2-4 delims=/- " %a in ('DATE/T') do echo %c%b%a