When I try to use SHIFT
inside of an IF
block I'm seeing some unexpected results. Using this:
@echo off
if "%1"=="/p" (
echo %1
shift
echo "shifted"
echo %1
)
I get the following:
C:\>ex.bat /p HAI
/p
"shifted"
/p
However, when I use this code:
@echo off
echo %1
shift
echo "shifted"
echo %1
I get this:
C:\>ex.bat /p HAI
/p
"shifted"
HAI
I need the second output, but in a logic block so I can loop over it. I'm trying to implement something similar to Jon's answer here: Using parameters in batch files at DOS command line, but I'm having some trouble. Why is this happening?
That is the expected behavior because %1
is expanded when the line is parsed, and the entire parenthesized IF construct is parsed all in one pass. So you cannot see the result of the SHIFT until a new line is parsed, which won't happen until after you have left your parenthesized block.
The same problem happens when expanding an environment variables using %var%
. With environment variables you can get around the problem by enabling delayed expansion using SETLOCAL EnableDelayedExpansion
and then using !var!
. But there isn't any comparable way to expand parameters using delayed expansion.
EDIT 2013-06-21 - There is not a comparable way, but there is a simple way that is relatively slow.
You can force the line to be reparsed by using CALL and doubling up the %
.
@echo off
if "%1"=="/p" (
echo %1
shift
echo "shifted"
call echo %%1
)
EDIT 2016-07-15
The code in Vladislav's answer is a bad practice, as it implies that you can jump back within a block of code after you used GOTO to leave it. That simply does not work. GOTO immediately kills any parsed code blocks. You might as well have written Vladislav's code as:
@echo off
if "%1"=="/p" (
goto :true
:true
echo "%1"
shift
echo shifted
echo "%1"
)
If the condition is FALSE, then the entire block is skipped.
If the condition is TRUE, then the GOTO immediately kills the block, and then execution picks up normally at the :true label (no block context). The extra )
at the end is simply ignored.
Note that you cannot add an ELSE with this construct. The following does not give the desired result:
@echo off
if "%1"=="/p" (
goto :true
:true
echo "%1"
shift
echo shifted
echo "%1"
) else (
echo This will execute regardless whether arg1 is /p or not
)
If FALSE, then only the ELSE block is executed.
if TRUE, then the top block is executed and immediately killed. The remainder of the code is executed, and the ) else (
line is ignored because )
functions
as a REM if the parser is expecting a command and there are no active parentheses blocks on the stack.
I strongly recommend that you never GOTO a label within a parenthesized block of code as I have shown above (or as Vladislav has done).
Below is a better (simpler and not misleading) way to do the same thing:
@echo off
if not "%1"=="/p" goto :skip
echo "%1"
shift
echo shifted
echo "%1"
:skip
You could support an IF/THEN/ELSE concept with some extra labels and GOTOs
@echo off
if not "%1"=="/p" goto :notP
echo "%1"
shift
echo shifted
echo "%1"
goto :endIf
:notP
echo not /p
:endIf
actually you can just perform shift outside if block:
@echo off
if "%1"=="/p" (
echo "%1"
goto:perform_shift;
:aftersift
echo "%1"
)
goto:end;
:perform_shift
shift
echo "shifted"
goto:aftersift;
:end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With