Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows batch shift not working in if block

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?

like image 867
piebie Avatar asked Jun 21 '13 17:06

piebie


2 Answers

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
like image 120
dbenham Avatar answered Nov 15 '22 13:11

dbenham


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
like image 30
Vladislav Varslavans Avatar answered Nov 15 '22 12:11

Vladislav Varslavans