Reputation: 34569
Can anyone explain to me why the following snippet prints 0
:
@echo off
setlocal
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo %ERRORLEVEL%
)
While adding another, equivalent statement outside of the for-loop makes it print 1 1
:
@echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL%
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo %ERRORLEVEL%
)
I'm a bit of a newbie to Batch, so this is kinda mysterious to me since the two statements seem to be unrelated. Any help with this would be appreciated, thanks!
Upvotes: 1
Views: 4602
Reputation: 1
@echo off
setlocal enabledelayedexpansion
:::This part is not exactly necessary but it creates the file dictionary.txt and the var - this
echo.word>dictionary.txt
set this=notaword
:::So instead of checking for errorlevel just check for a space in the output
:middle
for /f "tokens=*" %%a in ('findstr /n /b /c:%this% dictionary.txt') do (
set "output=%%a"
goto :yeah
)
:yeah
if exist %output% (""," "
goto :oops
) else (
goto :nice
)
:oops
echo.%this% is NOT a word
pause & set this=word & goto :middle
:nice
echo.%output%
pause
Upvotes: 0
Reputation: 80113
The issue is that within a code block
(parenthesised series of statements) any %var%
will be replaced by the actual value of the variable at parse time
.
Hin your first example, %errorlevel%
0
and echoed as such. In second example, it is 1
when the for
is encountered, hence it it replaced by 1
.
If you want to display a value of an environment variable that may be changed within a loop, then you need to do one of three things:
Invoke setlocal enabledelayedexpansion
and echo !var!
instead of %var%
- noting that the number of nested setlocal
instructions you can have active is limited.
Call a subroutine
Employ a syntax-exploit.
There are many, many articles about delayedexpansion
on SO.
Crudely, you could simply use (note - case is largely irrelevant in batch, except for the case of the loop-control variable (metavariable
- %%i
in this instance)
@echo off
setlocal ENABLEDELAYEDEXPANSION
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
)
Another way is to dynamically invoke setlocal
@echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
setlocal ENABLEDELAYEDEXPANSION
echo %ERRORLEVEL% or !errorlevel!
endlocal
)
The disadvantage of this is that the endlocal
backs out any changes made to the environment since that last setlocal
. Also note that if delayedexpansion
is not in effect, !
is no longer a special character.
Or, you can use errorlevel
in its traditional manner:
@echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
if errorlevel 1 (echo errorlevel is 1 or greater
) else (echo errorlevel is 0
)
)
Note that this looks at the run-time
value of errorlevel
and if errorlevel n
means "if errorlevel is n or greater than n"
Or - call a subroutine:
@echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
call :show
)
goto :eof
:show
echo %ERRORLEVEL%
goto :eof
Note here that goto :eof
(here the colon is important - this means "go to the physical end-of-file"
Or, a special version of using a subroutine - a syntax-exploit
@echo off
setlocal
echo blah | findstr bin > NUL
echo %ERRORLEVEL% or !errorlevel!
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
call echo %%errorlevel%%
)
Upvotes: 6
Reputation: 863
@echo off
setlocal ENABLEDELAYEDEXPANSION
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
echo !ERRORLEVEL!
)
this should work.
A for loop in cmd is technically one line, so the variable gets expanded only once. Using exclamation marks instead of percent and "ENABLEDELAYEDEXPANSION" should fix it.
Upvotes: 0
Reputation: 2603
UPDATED:
If you put your echo %ERRORLEVEL%
statement after the closing )
of the for statement it works as expected.
for /f %%i in ('cmd /c echo blah') do (
echo %%i | findstr bin > NUL
)
echo %ERRORLEVEL%
I'm kind of guessing here, for the exact 'definitive' reason, but setlocal
is definitely causing it. I'm assuming the value of the ERRORLEVEL within the for loop is the result of the cmd /c ...
statement. It seems like a strange behavior, but I've seen worse in batch files.
If you remove the setlocal
at the beginning it works as one would normally expect it to..
ORIGINAL:
The findstr
sets the errorlevel
when it runs. So, echo %errorlevel%
will always output 0.
For example the following findstr statement is successful, so it outputs the line with the match (the only line passed to it):
C:\>echo blahbin | findstr bin
blahbin
C:\>echo %errorlevel%
0 ; success / found it..
While this statement is not successful (findstr doesn't output anything) and the errorlevel is set to 1:
C:\>echo blah_in | findstr bin
C:\>echo %errorlevel%
1 ; failed
Upvotes: 0