How do you use setlocal in a batch file? I am just learning scripting and would like it explained to me in very simple terms.
I have a script that stops and says < was unexpected at this time it may have something to do with not having any setlocal statements in the script.
You make the first line SETLOCAL. This example is from the linked article below:
rem *******Begin Comment**************
rem This program starts the superapp batch program on the network,
rem directs the output to a file, and displays the file
rem in Notepad.
rem *******End Comment**************
#echo off
setlocal
path=g:\programs\superapp;%path%
call superapp>c:\superapp.out
endlocal
start notepad c:\superapp.out
The most frequent use of SETLOCAL is to turn on command extensions and allow delayed expansion of variables:
SETLOCAL ENABLEEXTENSIONS ENABLEDELAYEDEXPANSION
For more info on SETLOCAL see the Command Line Reference at Microsoft TechNet.
Direct link to Setlocal
Suppose this code:
If "%getOption%" equ "yes" (
set /P option=Enter option:
echo Option read: %option%
)
Previous code will NOT work becase %option% value is replaced just one time when the IF command is parsed (before it is executed). You need to "delay" variable value expansion until SET /P command had modified variable value:
setlocal EnableDelayedExpansion
If "%getOption%" equ "yes" (
set /P option=Enter option:
echo Option read: !option!
)
Check this:
set var=Before
set var=After & echo Normal: %var% Delayed: !var!
Guess what the output is...
Try this:
SET PATH=%PATH%;%~dp0;
This will get your local folder your are running the batch from and add it to the current path.
example: if your are running a .bat or .cmd from d:\tools\mybatch.bat
it will add d:\tools to the current path so that it may find additional files on that folder.
Related
I'm trying to use CMDER for a development environment that I've setup.
Basically I've created a .bat file that calls:
#ECHO OFF
start Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\Cmder.exe
Then I've placed the file startdev.bat in:
%CMDER_HOME%\config\profile.d
So everything seems to work just fine, but when the startdev.bat finishes, issuing an:
echo %PATH%
returns:
Z:\_DEV\OS_WINDOWS\1_COMPILER\JDK\ORACLE\1.8.0_181\bin;Z:\_DEV\OS_CYGWIN\bin;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CLutils;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\PUTTY;Z:\_DEV\OS_WINDOWS\6_VERSION_CONTROL\PortableGit\bin;C:\WINDOWS;C:\WINDOWS\SysWOW64;C:\WINDOWS\System32
...any idea what's happening?
I would either expect CMDER to override PATH with the value from its own settings, or use my full path, which before the startdev.bat ends shows the value of:
PATH=Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\vendor\conemu-maximus5;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\vendor\conemu-maximus5\ConEmu;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CMDER\vendor\conemu-maximus5\ConEmu\Scripts;Z:\_DEV\OS_ALL\JVM\3_BUILD_TOOLS\GRADLE\5.4\bin;Z:\_DEV\OS_ALL\JVM\3_BUILD_TOOLS\MAVEN\3.5.4\bin;Z:\_DEV\OS_ALL\JVM\3_BUILD_TOOLS\ANT\1.10.5\bin;Z:\_DEV\OS_WINDOWS\3_BUILD_TOOLS\NODE\LTS\10.15.3;Z:\_DEV\OS_WINDOWS\3_BUILD_TOOLS\NODE\LTS\10.15.3\node_modules;Z:\_DEV\OS_WINDOWS\1_COMPILER\GO\1.12.4\bin;Z:\_DEV\OS_WINDOWS\1_COMPILER\PYTHON\32bit\2.7.13;Z:\_DEV\OS_WINDOWS\1_COMPILER\PYTHON\32bit\2.7.13\scripts;Z:\_DEV\OS_WINDOWS\1_COMPILER\ANDROID\android-sdk-windows\platform-tools;Z:\_DEV\OS_WINDOWS\1_COMPILER\JDK\ORACLE\1.8.0_181\bin;Z:\_DEV\OS_CYGWIN\bin;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\CLutils;Z:\_DEV\OS_WINDOWS\9_MISC_TOOLS\PUTTY;Z:\_DEV\OS_WINDOWS\6_VERSION_CONTROL\PortableGit\bin;C:\WINDOWS;C:\WINDOWS\SysWOW64;C:\WINDOWS\System32
..but the fact that it only seems to be keeping the value as defined about halfway through the batch job is strange.
Any ideas?
First I recommend opening a command prompt window and run setlocal /? and endlocal /? to get displayed the help/documentation for those two commands. Very important to know is that every setlocal without a corresponding endlocal results in an implicit execution of endlocal by cmd.exe before exiting processing of a batch file or a subroutine called with command CALL.
Next I suggest reading this answer for even more details about the commands SETLOCAL and ENDLOCAL and what happens on using them.
I suggest like michael_heath to change this code block:
setLocal EnableDelayedExpansion
set CLASSPATH=.
for /R %JRE_HOME%\lib %%a in (*.jar) do (
set CLASSPATH=!CLASSPATH!;%%a
)
set CLASSPATH=!CLASSPATH!
Better would be:
setLocal EnableExtensions EnableDelayedExpansion
set CLASSPATH=.
for /R "%JRE_HOME%\lib" %%a in (*.jar) do set "CLASSPATH=!CLASSPATH!;%%a"
endlocal & set "CLASSPATH=%CLASSPATH%"
Now the local environment is ended with passing the environment variable CLASSPATH from local environment, on which it was defined, to the restored previous environment because of cmd.exe expands %CLASSPATH% to current value of the environment variable CLASSPATH in current local environment before executing the command endlocal which restores the previous environment.
Wrong in your batch file is also set WINDIR=%SystemRoot%;%SystemRoot% which should be set "WINDIR=%SystemRoot%".
I recommend further reading Why is no string output with 'echo %var%' after using 'set var = text' on command line? It explains why the syntax set "variable=string value" is recommended nowadays. Many of the environment variable definitions use directly or indirectly %UserProfile% which means depending on whatever the user currently running the batch file has entered as user name on creation of the user account. I have seen users entering their name containing a space and non ASCII characters. And I have seen users creating an account with a user name containing character & like Company GmbH & Co. An ampersand outside a double quoted argument string is interpreted as AND operator and cmd.exe tries to execute after set also the remaining string after & as command line on using something like set USERHOME=%DEVHOME%\%USERNAME% instead of set "USERHOME=%DEVHOME%\%USERNAME%". Well, startdev.bat redefines nearly all predefined Windows Environment Variables including USERNAME and USERPROFILE and so is written safe for most environment variable definitions.
This code block is also not optimal:
FOR /F "usebackq" %%i IN (`hostname`) DO SET HOSTNAME=%%i
echo Running on hostname: %HOSTNAME%
The host name respectively computer name could contain also a space or characters critical for command line or start with a semicolon for some unknown reason. So better would be:
FOR /F delims^=^ eol^= %%i IN ('hostname') DO SET "HOSTNAME=%%i"
setlocal EnableDelayedExpansion & echo Running on host name: !HOSTNAME!& endlocal
Whereby there is the environment variable COMPUTERNAME predefined by Windows making it possible to use just following command line:
setlocal EnableDelayedExpansion & echo Running on host name: !ComputerName!& endlocal
An ECHO command line containing an immediately expanded environment variable reference on which it is unknown if its value contains &|<> is always a problem because of the environment variable reference is expanded before further processing of the command line by cmd.exe as described at How does the Windows Command Interpreter (CMD.EXE) parse scripts?
I suggest also reading DosTips forum topic ECHO. FAILS to give text or blank line - Instead use ECHO/ and avoid the usage of echo. in the batch file to output an empty line.
"halfway through the batch job" as you have a
setLocal EnableDelayedExpansion which sets any further
changes to the variable PATH or other set variables as local.
The endLocal not specified is implied at the end of the script.
To resolve this, use endLocal and set CLASSPATH=%CLASSPATH%
on the same parsed line to set CLASSPATH as global.
Change this part:
setLocal EnableDelayedExpansion
set CLASSPATH=.
for /R %JRE_HOME%\lib %%a in (*.jar) do (
set CLASSPATH=!CLASSPATH!;%%a
)
set CLASSPATH=!CLASSPATH!
to this:
setLocal EnableDelayedExpansion
set CLASSPATH=.
for /R %JRE_HOME%\lib %%a in (*.jar) do (
set CLASSPATH=!CLASSPATH!;%%a
)
endLocal & set CLASSPATH=%CLASSPATH%
After that changed part, the script will set variables as global again.
This is my first batch script. I want to convert a list of files and use a substring of the input filename (Day of year). However the substring part doesn't work:
SETLOCAL EnableDelayedExpansion
FOR %G IN (%dirInp%%station%???0.DAT) DO (
SET filInp="%G"
SET doy=!filInp:~-9,3! rem this doesn't work?
rem Convert Trimble GPS receiver observations to RINEX
%dirExe%teqc -tr d %G > %dirOut%%station%!doy!0.%yy%D
)
So, how should I do this?
In addition to #Stephan 's comment I've updated your code to make a minimal working example (MWE) using some abstact file paths and substring parameters.
Than I moved SETLOCAL EnableDelayedExpansion inside the FOR loop and now it works for me on Windows 7.
I also closed SETLOCAL EnableDelayedExpansion by endlocal (could be sensitive for further code excecution
#echo off
FOR %%G IN (C:\DirInput_StationA\201901.DAT C:\DirInput_StationB\201812.DAT) DO (
SETLOCAL EnableDelayedExpansion
SET "filInp=%%G"
SET "doy=!filInp:~-10,4!"
rem Convert Trimble GPS receiver observations to RINEX
REM Changed your call to echo of the vars
echo G: "%%G"; doy: "!doy!"
endlocal
)
I am fighting with little piece of code for last two days.
In this I am not able to set variable in a for loop.
I want to assign a filename to a variable for string manipulation.
echo off
for /f %%a IN ('dir /b *_ah.ttf') DO (
set /a fName=%%~na
echo %fName%
)
When I echo fName variable I get only last filename repeatedly number of times for for loop count.
(I want to pass this variable as an argument to some batch file as follows
ttfhnt --strong-stem-width=D -i %%a %fName:~0,-3%.ttf
but its failing due to above problem)
Can somebody help me please?
When the cmd parser reads a line or a block of lines (the code inside the parenthesis), all variable reads are replaced with the value inside the variable before starting to execute the code. If the execution of the code in the block changes the value of the variable, this value can not be seen from inside the same block, as the read operation on the variable does not exist, as it was replaced with the value in the variable.
This same behaviour is seen in lines where several commands are concatenated with &. The line is fully parsed and then executed. If the first commands change the value of a variable, the later commands can not use this changed value because the read operation replace.
To solve it, you need to enable delayed expansion, and, where needed, change the syntax from %var% to !var!, indicating to the parser that the read operation needs to be delayed until the execution of the command.
And set /A is only used for arithmetic operations
setlocal enabledelayedexpansion
for /f "delims=" %%a IN ('dir /b *_ah.ttf') DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
edited to adapt to comments
While for command is able to execute a command (in the OP code, the dir...), retrieve its output and then iterate over the lines in this output, the original reason for the command is to iterate over a set of files. In this form, the code can be written as
setlocal enabledelayedexpansion
for %%a IN ("*_ah.ttf") DO (
set "fName=%%~na"
echo "!fName!" "!fName:~0,-3!"
)
Now, the for command replaceable parameter will iterate over the indicated set of files. (execute for /? for a list of all the command options).
But as foxidrive points, the problem with delayed expansion are the exclamation signs. Without delayed expansion, they are another normal character, but with delayed expansion they frequently become a problem when a value containig them is assigned/echoed.
A quick test
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
echo normal : %test%
for /f "delims=" %%a in ("!test!") do echo for : %%a
Will show
---------------------
test=this is a test!
---------------------
delayed : this is a test!
normal : this is a test
for : this is a test
Obviously when the value is a file name, this behaviour will make the code find or not the file.
Depending on the case different solutions can be used, but usually it involves the activation / desactivation of the delayed expansion behaviour (beware, the endlocal removes any change in environment variables from the previous setlocal).
#echo off
setlocal enabledelayedexpansion
set "test=this is a test^!"
echo ---------------------
set test
echo ---------------------
echo delayed : !test!
rem Commuted to no delayed expansion
setlocal disabledelayedexpansion
echo normal : %test%
endlocal
rem Cancelled the initial enable delayed expansion
for /f "delims=" %%a in ("!test!") do endlocal & echo for : %%a
rem The last endlocal has removed the changes to the variable
echo no data : [%test%]
It's a simple bat script that should checkout some folders from svn . I'm not that up on bat scripting, there seems to be no consistency on how variables are referenced.
For instance the variable "branchV" does not get appended it is seen as '""', but if I echo it I see the user input.
set "DB_DIRECTORIES=AuditUser-db CarrierProcessingRules-db iDetectDB-db iRisk-db WarningsIndex-db"
set "SVNBASEURL=http://XX.XX.XX.XX:7777/svn/YYY"
set BASELOCALDIRECTORY="C:"
#echo on
#cls
#echo Check out DB directories from?
#echo
#echo 1. Trunk
#echo 2. Branch
#echo
#echo
#set OPTIONSELECTED=
#set /P OPTIONSELECTED=SELECT OPTION:%=%
if "%OPTIONSELECTED%" == "1" (
set SVNURL="%SVNBASEURL%/trunk"
set BRANCHV="BCSTrunk"
) ELSE IF %OPTIONSELECTED% == 2 (
#echo
#echo
#echo
#echo PLEASE ENTER THE BRANCH VERSION YOU WISH TO CHECKOUT
#echo
#echo
#set branchV=
#set /P branchV=ENTER VERSION:%=%
#echo
set SVNURL="%SVNBASEURL%"/branches/"%branchV%"
) ELSE (
#echo Invalid option
)
for %%i in (%DB_DIRECTORIES%) do (
set PATHTOUSE="%BASELOCALDIRECTORY%"/"%branchV%"/%%i
set NEWSVNURL="%SVNURL%"/%%i
REM Intended to remove all inverted commas , which were causing an issue in svn
set PATHTOUSE="%PATHTOUSE:=%"
set NEWSVNURL=%NEWSVNURL:=%
TortoiseProc.exe /command:checkout /path:%PATHTOUSE% /url:%NEWSVNURL% /closeonend:1
)
In batch scripts, when a line or a block of code (the code enclosed in parenthesis) is reached, all variable reads are replaced with the value they have before starting to execute that line or block. So, if you change a variable inside a block, you can not retrieve this value inside the same block. There are no reads of the variable value, they were replaced with its values.
To correctly retrieve the changed value inside the same block, it is necessary to indicate to cmd that the read operations should be delayed until the line is executed. To do this, two steps are necessary. First is enable this option
setlocal enabledelayedexpansion
When it is enabled, variables that should be read delayed need the sintax !var! instead of %var%
Why does the following Windows Batch File output Foo followedby Bar, rather than Baz?
#echo off
setlocal
set _=Foo
echo %_%
set _=Bar
if 1==1 (
set _=Baz
echo %_%
)
The output on my system (Microsoft Windows XP [Version 5.1.2600]) is:
Foo
Bar
If I remove the conditional statement, the expected output of Foo and Baz is observed.
What's happening is that variable substitution is done when a line is read. What you're failing to take into account is the fact that:
if 1==1 (
set _=Baz
echo %_%
)
is one "line", despite what you may think. The expansion of "%_%" is done before the set statement.
What you need is delayed expansion. Just about every single one of my command scripts starts with "setlocal enableextensions enabledelayedexpansion" so as to use the full power of cmd.exe.
So my version of the script would be:
#echo off
setlocal enableextensions enabledelayedexpansion
set _=Foo
echo !_!
set _=Bar
if 1==1 (
set _=Baz
echo !_!
)
endlocal
This generates the correct "Foo", "Baz" rather than "Foo", "Bar".
The answer to this is the same as the answer to:Weird scope issue in batch file. See there for more details. Basically variable expansion is done at line read time, not at execution time.
try this
#echo off
setlocal
set _=Foo
echo %_%
set _=Bar
if "1" NEQ "2" goto end
set _=Baz
echo %_%
:end