13

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?

Community
  • 1
  • 1
piebie
  • 2,652
  • 21
  • 30
  • I ended up finding the answer here: [Shift doesn't work in batch script](http://social.technet.microsoft.com/Forums/scriptcenter/en-US/7bbbe8df-e3c0-46ab-aede-396c2b6f6184/shift-doesnt-work-in-batch-script) – piebie Jun 21 '13 at 18:05
  • if "%1"=="/p" (echo %1) makes no sense, because you know what the content of %1 is! to get the next parameter use echo %2. see my [answer](https://stackoverflow.com/a/50886348/2369575) – Kux Jun 16 '18 at 08:52

4 Answers4

16

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
Community
  • 1
  • 1
dbenham
  • 127,446
  • 28
  • 251
  • 390
2

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
Vladislav Varslavans
  • 2,775
  • 4
  • 18
  • 33
  • 1
    That "sort of works", but it is very bad form. It implies you are jumping within a parenthesized block, which does not work. The moment you use GOTO within a block, you kill the block; the parentheses are there, but they serve no purpose. This is not important in this case, but very important if the block is part of a FOR loop. If you GOTO within a FOR loop, then the loop is killed. – dbenham Jul 15 '16 at 22:12
  • see [EDIT 2016-07-15 in my answer](http://stackoverflow.com/a/17241649/1012053) for more information as to why this answer is not a good idea, as well as a suggestion for a better way to effectively do the same thing. – dbenham Jul 15 '16 at 22:43
0

The shift comands has no immediately effect inside the block, but outside.

Assuming you would like to read parameters as key value pairs:

:Parse
if "%1" equ "/p" (
    set P=%2
    shift
    shift
)
if "%1" equ "/q" (
    set Q=%2
    shift
    shift
)
if "%1" neq "" goto Parse
echo P: %P%
echo Q: %Q%
Kux
  • 1,362
  • 1
  • 16
  • 31
0

To achieve what you want you can use additional CALL it is needed when shifting in brackets:

@echo off

if "%1"=="/p" (
    echo %1
    shift
    echo "shifted"
    call echo %%1
)

example run:

>ex.bat /p HAI

/p
"shifted"
HAI
npocmaka
  • 55,367
  • 18
  • 148
  • 187