Windows batch shift not working in if block

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

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