Search code examples
windowsbatch-filecmdenvironment-variablesdelayedvariableexpansion

Variables are not behaving as expected


I've been wrestling trying to get the syntax right on this batch file and I cannot figure out why some things aren't working.

  1. The variable i is not getting incremented each time I do it.
  2. Concatenation on strc doesn't seem to concatenate.

Here is my code:

set i=0
set "strc=concat:"

for %%f in (*.mp4) do (
    set /a i+=1
    set "str=intermediate%i%.ts"

    set strc="%strc% %str%|"

    ffmpeg -i "%%f" -c copy -bsf:v h264_mp4toannexb -f mpegts "%str%"
)

set strc="%strc:-1%"
ffmpeg -i "%strc%" -c copy -bsf:a aac_adtstoasc Output.mp4

Solution

  • You are not the first, who fell into the famous "delayed expansion trap" (and you won't be the last).

    You need delayed expansion if you want to use a variable, that you changed in the same block (a block is a series of commands within parentheses (and )).

    Delayed variables are referenced with !var! instead of %var%.

    Reason is the way, cmd parses the code. A complete line or block is parsed at once, replacing normal variables with their value at parse time. Delayed variables are evaluated at runtime.

    Two simple batch files to demonstrate:

    setlocal EnableDelayedExpansion
    set "var=hello"
    if 1==1 (
        set "var=world"
        echo %var% !var!
    )
    
    setlocal EnableDelayedExpansion
    for /L %%i in (1,1,5) do (
        echo %random% !random!
    )
    

    Note: A line is also treated as a block:

    set "var=old"
    set "var=new" & echo %var% 
    

    With delayed expansion:

    setlocal EnableDelayedExpansion
    set "var=old"
    set "var=new" & echo !var! 
    

    Delayed expansion is per default turned off at the command prompt. If you really need it, you can do:

    cmd /V:ON /C "set "var=hello" & echo !var!"
    

    Also there is a way to do the same without delayed expansion (but call costs some time, so it's slower, but if for some reason you can't / don't want to use delayed expansion, it's an alternative):

    setlocal DISabledelayedexpansion
    for /L %%i in (1 1 5) do (
        call echo %random% %%random%% 
    )
    

    Both methods can also be used to display array-like variables:

    (This is often asked like "variable which contains another variable" or "nested variables")

    Here is a collection for using such array-like variables in different situations:

    With delayed expansion:

    setlocal ENableDelayedExpansion
    set "num=4"
    set "var[%num%]=HELLO"
    echo plain delayed: !var[%num%]!
    for /L %%i in (4 1 4) do (
        echo for delayed: !var[%%i]!
        set a=%%i
        call echo for delayed with variable: %%var[!a!]%%
    )
    

    without delayed expansion:

    setlocal DISableDelayedExpansion
    set "num=4"
    set "var[%num%]=HELLO"
    call echo plain called: %%var[%num%]%%
    for /L %%i in (4 1 4) do (
        call echo FOR called: %%var[%%i]%%
        set a=%%i
        call echo FOR called with variable: %%var[%a%]%%
    )
    

    Note: setlocal has no effect outside of batchfiles, so delayedexpansion works only:

    • In batch files
    • When the cmd was started with delayed expansion enabled (cmd /V:ON) (by default, the cmd runs with delayed expansion disabled)

    (Follow the links, when you are interested in the technical background or even the advanced technical stuff)