I get an error for my batch code
was unexpected at this time
I have removed all the comments and matched all opening brackets with counter closing ones, but still getting the same error.
What does the error mean?
The most probable cause of this error is a %variable% expansion used inside parentheses, because in this case the expanded value is the one the variable had before entering the ( block ). A simple example:
#echo off
for /L %%i in (1,1,10) do (
set /P input=Enter input value:
if %input% == One (
echo Option "one" selected
)
if %input% == Two (
echo Option "two" selected
)
. . .
)
In previous example the %input% value is always expanded to nothing because that variable does not exist before the FOR loop executed, so the if's are expanded to if == One ( and cause the above mentioned error. Remember that %var% expansion is achieved just one time when the command to execute is parsed before the execution. In this case, the command to execute is the external FOR, so all %var% expansions included into it at any level have this effect.
The way to solve this problem is via Delayed Expansion, that is, include setlocal EnableDelayedExpansion command at beginning and replace all %var% expansions by !var! ones. This way, the value of !var! variable will be expanded each time the command in which it appear be executed. Type set /? and cmd /? for further details on this topic.
Related
i have a batch script like this:
#echo off
setlocal
some commands to set variable %equal%
if %equal%==no (
some commands to set variable %equall% (including a call to a separate batch file)
if %equall%==no (
other commands (including a call to a separate batch file)
)
) else (
echo nothing new
)
endlocal
exit /b
But i getting this error (translated from spanish, so it can be innacurate): "(" was not expected at this moment on the first if sentence.
However, if i remove the inner if condition, it runs fine....
What am i missing?
you've got a delayed expansion problem. The variable equall gets defined inside a code block, so %equall% is still not defined. That makes your line
if %equall%==no (
to be parsed as
if ==no (
which leads to the mentioned error "(" is unexpected at this time"
To correct it, enable delayed expansion with the setlocal enabledelayedexpansion command and use delayed expansion with ! syntax instead of % syntax:
setlocal enabledelayedexpansion
if 1==1 (
set "var=hello"
echo !var!
)
Also you should get used to use a better syntax with if (quoting both sides of the comparison):
if "%var%" == "value" ...
(or with delayed expansion: if "!var!% == "value") Even if the variable is empty, this gets parsed as:
if "" == "value" ...
which is perfectly valid syntax.
I used a workaround, that is to send everything inside the outer if to a label:
#echo off
setlocal
some commands to set variable %equal%
if %equal%==no (
goto :label
) else (
echo nothing new
)
endlocal
exit /b
:label
some commands to set variable %equall% (including a call to a separate batch file)
if %equall%==no (
other commands (including a call to a separate batch file)
)
I have the following snippet (some code omitted):
REM Verify destination_folders.
FOR %%f in (%destination_folders%) do (
IF NOT EXIST %%f (
echo(
echo Destination folder %%f does not exist. Is version %version% correct? Please update the script with your required parameters.
SET /p continue="Do you want to abort the operation? [Y/N]:"
echo cont=%continue%
IF %continue:~0,1%==y (
goto :eof
)
IF %continue:~0,1%==Y (
goto :eof
)
)
)
The issue is that the variable continue is not being assigned until after the script has been executed. For example, if continue was N from the the previous execution and then I input Y it will not update it, hence the echo after the assignment will output cont=N although it should be cont=Y. What am I doing wrong?
You need delayed expansion. setlocal enabledelayedexpansion somewhere, and use !continue! instead of %continue%. The problem is that %continue% is expanded as the entire parenthetical code block is read for the first time, so it's treated as flat text by the time for performs its first loop. !continue! retains its variability, and it's re-evaluated on each iteration.
You can also avoid the delayed expansion problem by using choice and if errorlevel like this:
REM Verify destination_folders.
FOR %%f in (%destination_folders%) do (
IF NOT EXIST "%%~f" (
echo(
echo Destination folder %%f does not exist. Is version %version% correct?
echo Please update the script with your required parameters.
choice /N /M "Do you want to abort the operation? [Y/N]: "
if not errorlevel 2 goto :EOF
)
)
To learn in greater detail about delayed expansion, read this portion of the help set documentation:
Finally, support for delayed environment variable expansion has been added. This support is always disabled by default, but may be enabled/disabled via the /V command line switch to CMD.EXE. See CMD /?
Delayed environment variable expansion is useful for getting around the limitations of the current expansion which happens when a line of text is read, not when it is executed. The following example demonstrates the problem with immediate variable expansion:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "%VAR%" == "after" #echo If you see this, it worked
)
would never display the message, since the %VAR% in BOTH IF statements is substituted when the first IF statement is read, since it logically includes the body of the IF, which is a compound statement. So the IF inside the compound statement is really comparing "before" with "after" which will never be equal. Similarly, the following example will not work as expected:
set LIST=
for %i in (*) do set LIST=%LIST% %i
echo %LIST%
in that it will NOT build up a list of files in the current directory, but instead will just set the LIST variable to the last file found. Again, this is because the %LIST% is expanded just once when the FOR statement is read, and at that time the LIST variable is empty. So the actual FOR loop we are executing is:
for %i in (*) do set LIST= %i
which just keeps setting LIST to the last file found.
Delayed environment variable expansion allows you to use a different character (the exclamation mark) to expand environment variables at execution time. If delayed variable expansion is enabled, the above examples could be written as follows to work as intended:
set VAR=before
if "%VAR%" == "before" (
set VAR=after
if "!VAR!" == "after" #echo If you see this, it worked
)
set LIST=
for %i in (*) do set LIST=!LIST! %i
echo %LIST%
In one of my scripts, I need to use variables that contain parenthesis inside IF statements, but either the string is missing a closing parenthesis or the script exits prematurely with * was unexpected at this time (not actually an asterisk), depending on the scenario.
Example
#echo off
SET path=%programFiles(x86)%
echo Perfect output: %path%
IF NOT "%path%" == "" (
REM Variable is defined
echo Broken output: %path%
)
pause >nul
Output
Perfect output: C:\Program Files (x86)
Broken output: C:\Program Files (x86
I think/know that this is because it thinks the closing parenthesis in C:\Program Files (x86) is the end of the IF statement and it exits before the echo is complete.
Is there a simple way to cirumvent this? Preferably without resorting to
single-line IF statements, as I need to run more than one line of code within them,
copious amounts of GOTOs, as it's not practical,
SETLOCAL EnableDelayedExpansion and using !path! instead of %path%, as I recall reading somewhere that that method doesn't work consistently across OSs.
If not, I'll happily accept the most reliable solution offered, whatever it is.
(The scenario isn't up for debate. This is just a refined, concentrated example of the problem. The structure needs to be like this, as it is in my actual script, for reasons I won't go into. It's besides the point and it'll just confuse things and distract from the actual issue.)
First off - you should never use the PATH variable for your own use. It is a reserved environment variable. Using it for your own purposes can break your scripts.
The simplest solution really is to use delayed expansion. As long as your platform uses CMD.EXE then you have access to delayed expansion.
But there is a relatively easy way to make it work without delayed expansion. You can use disappearing quotes. The quote exists at parse time as the name of a FOR variable while the command is parsed. It expands to nothing before execution time.
#echo off
SET mypath=%programFiles(x86)%
echo Perfect output: %mypath%
IF NOT "%mypath%" == "" (
REM Variable is defined
for %%^" in ("") do echo fixed output: %%~"%mypath%%%~"
)
pause >nul
EDIT - When to use delayed expansion: Response to comment
I generally only use delayed expansion when it is needed (or more precisely, when it is advantageous). That being said, I usually find it advantageous in some portion of my batch code.
Major Advantages
Inside a code block in order to see changes to a variable within the block
When dereferencing the name of a variable. If a variable name is passed in as a parameter, the value of the variable can be gotten via delayed expansion: echo !%1!
When using variables as arguments to search and replace or substring operations: echo !var:%search%=%replace%!, echo !var:%start%,%len%!.
Whenever I need to expand the value and not worry about special characters within it needing escaping or quoting: set "var=A&B" & echo !var!
There are other methods to do the above (except the last), but delayed expansion is the easiest, most efficient (fastest to execute), and most reliable option.
Major Disadvantage
Any FOR variable that contains ! in its value will be corrupted when it is expanded if delayed expansion is enabled. I frequently toggle delayed expansion on and off within a FOR loop to get around the problem.
It is not good for executing a "macro" (executing code contained within a variable value) because many important phases of command parsing take place prior to the delayed expansion. So many batch features are unavailable to "macros" that are executed via delayed expansion.
my suggestion is :
if (condition_TRUE) goto goodbye_parenthesis_BEGIN
goto goodbye_parenthesis_END ----- line when previous condition is FALSE ----
:goodbye_parenthesis_BEGIN ----- line when previous condition is TRUE ----
...
variable treatment
...
:goodbye_parenthesis_END
The ) from the resolved variable in your echo statement is prematurely closing the IF block.
Ordinarily, you could fix that by escaping the ) with ^), but you can't modify the environment variable to resolve to C:\Program Files (x86^).
You can prevent this issue by surrounding the variable with quotes.
As a simpler example:
> SET bad=a)b
> IF 1 == 1 ( ECHO %bad% )
b was unexpected at this time.
> IF 1 == 1 ( ECHO "%bad%" )
"a)b"
As others already pointed out, the unescaped and unquoted closing parenthesis ) unintentionally ends the parenthesised if block.
Besides escaping, quotation, delayed expansion and "disappearing quotes", there are the following further options:
Use a for meta-variable on the quoted value and remove the quotes by the ~-modifier:
#echo off
set "PATH=%ProgramFiles(x86)%"
echo Perfect output: %PATH%
if not "%PATH%" == "" (
rem Variable is defined
for %%P in ("%PATH%") do echo Unbroken output: %%~P
)
pause > nul
Use the call command to initiate another variable expansion phase, together with doubled (escaped) %-symbols:
#echo off
set "PATH=%ProgramFiles(x86)%"
echo Perfect output: %PATH%
if not "%PATH%" == "" (
rem Variable is defined
call echo Unbroken output: %%PATH%%
)
pause > nul
Do escaping by sub-string substitution, which happens before ^-escaping is detected:
#echo off
set "PATH=%ProgramFiles(x86)%"
echo Perfect output: %PATH%
if not "%PATH%" == "" (
rem Variable is defined
echo Unbroken output: %PATH:)=^)%
)
pause > nul
Forgive me if I'm reading this wrong, but isn't the "NOT" causing control to enter the bracketed if and run the broken output?
what about:
#echo off
echo Perfect output: %programFiles(x86)%
IF NOT "%programFiles(x86^)%" == "" (
REM Variable is defined
echo Broken output: %programFiles(x86)%
)
pause >nul
?
I'd like to put each of the many properties' file names into variable fileName and echo them out to the command prompt window. But only the last properties file name to be cycled thru is printed out as many times as there are properties files. Is there an easy fix to this problem. I know that ...DO echo %%-nxG can do the same thing but I'd like to save the file name in %%~nxG for future use.
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo %fileName%
)
You need to use delayed expansion:
setlocal enabledelayedexpansion
FOR %%G IN (C:\ExecutionSDKTest_10.2.2\*.properties) DO (
set fileName=%%~nxG
echo !fileName!
)
Environment variables in cmd are expanded when a command is parsed – in this case this includes the whole block in parentheses. So %fileName% gets replaced by an empty string because it didn't have a value before the loop ran. Delayed expansion uses ! instead of % and changes variable evaluation so that they are evaluated just before a command is run.
help set has more details about why and when it is necessary. In general, whenever you modify and use a variable within a loop you have to use delayed expansion, but it comes with a few other benefits too.
My batch file terminates prematurely after I assign the first environmental variable (script output below). I've tried turning echo on, using errorlevels, sending the output to a text file, and checking syntax. I've spent several hours researching debugging batch scripts, but I have finally hit a brick wall.
Script's Goal: Search each directory name of the user's Program Files, looking for common antivirus programs. I realize that it would be easiest iterate through an array of antivirus names for this purpose, but I want to keep it simple for now.
#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 echo %ERRORLEVEL%
else echo "env. variable created successfully."
for /d %%f in (""%ProgramFiles%\*"") do (
{
IF NOT ERRORLEVEL 0 echo %ERRORLEVEL%
echo "%%f"
if exist /i "*McAfee*" < %%f %AntiVirus1%="McAfee"
::find "Norton" < %%f
::find "Comodo" < %%f
::find "AVG" < %%f
}
echo %AntiVirus1%
#pause
Output of this script:
C:\Users\Matt\Desktop>set AntiVirus1="Initial Value"
C:\Users\Matt\Desktop>
Can someone point me to what I'm doing wrong?
UPDATE Corrected script, now working but returning incorrect results:
::#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 (echo %ERRORLEVEL%) ELSE echo "env. variable created successfully."
echo Checking Program Files...
for /d %%f in ("%ProgramFiles%\*") do (
echo "%%f"
if %%f=="*adobe*" set AntiVirus1="adobe"
)
echo %AntiVirus1% found
#pause
First of all, ELSE must be on the same line with IF or on the same line with the closing parenthesis that pertains to IF. In you particular case you should change your first IF...ELSE command like this:
IF NOT ERRORLEVEL 0 (ECHO %ERRORLEVEL%) ELSE ECHO "env. variable created successfully."
or like this:
IF NOT ERRORLEVEL 0 (
ECHO %ERRORLEVEL%
) ELSE ECHO "env. variable created successfully."
(Capitalisation and indentation are perfectly optional.)
Other issues:
Duplicated quotation marks in the FOR loop header:
for /d %%f in (""%ProgramFiles%\*"") do (
should be
for /d %%f in ("%ProgramFiles%\*") do (
Braces ({, }) around the loop body. They are not part of the loop syntax (in fact, they are not part of batch scripting syntax at all), so should be dropped.
No closing parenthesis matching the opening one after DO. It should be added on a separate line after the loop body.
Incorrect use of ::-style comments in the loop body. They are not allowed inside bracketed blocks. Use REM instead.
UPDATE
In batch scripting, testing for a substring is done somewhat unusually. You'll need another environment variable and you'll also need to enable delayed expansion. The latter is not really connected with the comparison, but it is needed because the comparison is going to be performed within a bracketed block.
Here's your new script modified, with the changes highlighted:
::#echo off
::variables
set AntiVirus1="Initial Value"
IF NOT ERRORLEVEL 0 (echo %ERRORLEVEL%) ELSE echo "env. variable created successfully."
SETLOCAL EnableDelayedExpansion
echo Checking Program Files...
for /d %%f in ("%ProgramFiles%\*") do (
echo "%%f"
SET "folder=%%f"
if /I NOT "!folder:adobe=!"=="!folder!" set AntiVirus1="adobe"
)
echo %AntiVirus1% found
#pause
Here's a bit of explanation.
The ! syntax is a delayed expansion equivalent of % and is used with environment variables only, not with loop variables and not with command line parameters. Delayed expansion is needed because we are in a bracketed block. A bracketed block is parsed entirely before it starts executing, so all %var% expressions are expanded (evaluated) before the block starts and are not changed throughout the block's execution. That cannot suit us because we need to assign different values to a variable during the block's execution, and the values must be read within the block. Delayed expansion, as follows from the name, delays the expansion of a variable until the actual execution of every single command that references that variable. Because immediate expansion can still be used alongside delayed expansion, a different syntax is introduced, which is ! around variable names, instead of %.
!folder:adobe=! means evaluate folder replacing every occurrence of adobe with an empty string. The result of this expression is then compared to the (unchanged) value of folder. If there's a match, then the replacement didn't occur, which means there was no adobe in the value of folder in the first place. In this case we should do nothing. But if there was not a match, i.e. if the modified value didn't match the unmodified one, then we should set the AntiVirus1 variable. This is why there's NOT in front of the comparison.
The /I option simply means case-insensitive comparison.