none
Batch variable not updating as expected

    Question

  • I have the following batch file, and it does not work exactly as expected. 

    for /f "delims=*" %%A in ('dir /b/tc/a-d c:\scripts') do (
        set fdate=%%~tA
        echo %fdate%
        echo %%~fA
        echo %%~tA
    )
    

    The last 2 echos give me the correct output (the filename and the date).  However, the %fdate% variable never seems to be changed, and just stays on one value.

    Can anyone help with this?


    [string](0..9|%{[char][int](32+("39826578840055658268").substring(($_*2),2))})-replace "\s{1}\b"
    Wednesday, October 5, 2011 7:28 PM

Answers

  • Generations of batch file programmers have tripped over this one. Here it goes.

    When the command processor interprets a batch file, it does it like this:
    - Read a line of code.
    - Perform whatever variable substitution is necessary.
    - Execute it.
    - Read the next line of code.
    - etc.

    And here comes the catch: When you use parenthesis in your code then everything within the parenthesis counts as one single line that gets resolved in one fell swoop, regardless of the individual steps. In your original code this means that fdate is set to some value but the subsequent line (echo %fdate%) is not a line of its own and it therefore does not yet "know" the value of %fdate%.

    How to get around it? You need to take two measures:
    a) Tell the command processor to delay resolution of variables until it executes them.
    b) Surround the variables with exclamation marks if you want them to be subject to delayed expansion.

    When you observe these two rules then you can do anything you like with your variables.

    • Marked as answer by Bigteddy Wednesday, October 5, 2011 8:38 PM
    Wednesday, October 5, 2011 8:21 PM
  • Try this:

    @echo off
    SetLocal EnableDelayedExpansion
    for /f "delims=*" %%A in ('dir /b/tc/a-d c:\scripts') do (
        set fdate=%%~tA
        echo !fdate!
        echo %%~fA
        echo %%~tA
    )

    • Marked as answer by Bigteddy Wednesday, October 5, 2011 8:04 PM
    Wednesday, October 5, 2011 7:35 PM

All replies

  • Try this:

    @echo off
    SetLocal EnableDelayedExpansion
    for /f "delims=*" %%A in ('dir /b/tc/a-d c:\scripts') do (
        set fdate=%%~tA
        echo !fdate!
        echo %%~fA
        echo %%~tA
    )

    • Marked as answer by Bigteddy Wednesday, October 5, 2011 8:04 PM
    Wednesday, October 5, 2011 7:35 PM
  • Hi Pegasus,

    Can you please explain about the !fdate! ?  Why can't I use %fdate% ?  Also, what does the line:

    SetLocal EnableDelayedExpansion

    ...do?

    The thing I want to do is to take the date apart, and put it back together in a different format.  So my code will be something like this:

    set mm=%fdate:~3,2%
    set dd=%fdate:~0,2%
    set yyyy=%fdate:~6,4%
    set targetdate=%yyyy%%mm%%dd%


    [string](0..9|%{[char][int](32+("39826578840055658268").substring(($_*2),2))})-replace "\s{1}\b"
    • Edited by Bigteddy Wednesday, October 5, 2011 8:09 PM
    Wednesday, October 5, 2011 8:08 PM
  • Generations of batch file programmers have tripped over this one. Here it goes.

    When the command processor interprets a batch file, it does it like this:
    - Read a line of code.
    - Perform whatever variable substitution is necessary.
    - Execute it.
    - Read the next line of code.
    - etc.

    And here comes the catch: When you use parenthesis in your code then everything within the parenthesis counts as one single line that gets resolved in one fell swoop, regardless of the individual steps. In your original code this means that fdate is set to some value but the subsequent line (echo %fdate%) is not a line of its own and it therefore does not yet "know" the value of %fdate%.

    How to get around it? You need to take two measures:
    a) Tell the command processor to delay resolution of variables until it executes them.
    b) Surround the variables with exclamation marks if you want them to be subject to delayed expansion.

    When you observe these two rules then you can do anything you like with your variables.

    • Marked as answer by Bigteddy Wednesday, October 5, 2011 8:38 PM
    Wednesday, October 5, 2011 8:21 PM
  • Thanks for that very clear answer.  It helped me to build my batch file, which checks if a file is after a date that is entered manually.  I'm not very experienced with batch file programming, so if you see another way, please let me know.  Here's the code so far:

     

    echo off
    SetLocal EnableDelayedExpansion
    set/p targetdate=Enter search date (yyyymmdd):  
    
    echo targetdate: %targetdate%
    
    for /f "delims=*" %%A in ('dir /b/tc/a-d c:\scripts') do (
         set fdate=%%~tA
         set mm=!fdate:~3,2!
         set dd=!fdate:~0,2!
         set yyyy=!fdate:~6,4!
         set filedate=!yyyy!!mm!!dd!
         if !filedate! gtr %targetdate% echo Recent file found %%~fA
    ) 

     


    [string](0..9|%{[char][int](32+("39826578840055658268").substring(($_*2),2))})-replace "\s{1}\b"
    • Edited by Bigteddy Wednesday, October 5, 2011 8:41 PM
    Wednesday, October 5, 2011 8:38 PM
  • Hi Bigteddy,

    Any reason you're not using PowerShell ? :)

    Bill

    Wednesday, October 5, 2011 8:42 PM
    Moderator
  • A very good question, Bill.  This was prompted by a question someone else asked about zipping files and deleting files older than a certain date.  As it was specifically asked for a batch file, I thought I'd take the opportunity and learn a bit about batch file programming.

    You know I LOVE Powershell, and I look forward to the day when cmd and VBS are just fond memories, and the only  command processor/shell you will have will be your Powershell prompt (or ISE).

    Mark my words, as more and more XP and 2003 machines are replaced, this will happen eventually.


    [string](0..9|%{[char][int](32+("39826578840055658268").substring(($_*2),2))})-replace "\s{1}\b"
    Wednesday, October 5, 2011 8:51 PM
  • Thanks for that very clear answer.  It helped me to build my batch file, which checks if a file is after a date that is entered manually.  I'm not very experienced with batch file programming, so if you see another way, please let me know. 

    This looks fine. As a very minor point, I would replace "echo off" with "@echo off".
    Wednesday, October 5, 2011 9:15 PM
  • Don't hold your breath waiting for cmd and vbscript to no longer be there. But you can make them fond memories just by choosing to use PowerShell instead.

     

    Wednesday, October 12, 2011 3:34 AM
  • :: Perhaps the following sample batch with logging could be a demo for a case with using date and time:

    ::
    @echo off
    :: -----------------------------------------------------------------
    :: -----------------------------------------------------------------
    :: Create a time-stamp
       for /F "tokens=*" %%d IN ('date /T') do set D__St=%%d&::
       for /F "tokens=*" %%t IN ('time /T') do set Tm__St=%%t&::
       set D_St=%D__St%&::
       set Tm_St=%Tm__St%&::
       set D_St=%D_St:/=_%&::
       set D_St=%D_St: =-%&::
       set Tm_St=%Tm_St::=.%&::
       set DT_St=%D_St%%Tm_St%&::
       set DT__St=%DT_St: =%&::
    :: -----------------------------------------------------------------
       set t=Test - %~nx0      %Tm__St% %D__St%&::
       title %t%
    :: -----------------------------------------------------------------
       set Log=%~dpn0_%DT__St%.log

       if not exist "%Log_Path%\." >> nul 2>&1 md "%Log_Path%"
    :: -----------------------------------------------------------------
    :: Prepare logging var's
       set wl=  call :write_log&                  :: Log a Message with a Time Stamp
       set ewl= call :echo_write_log&             :: Display a Message and Log it with a Time Stamp
       set etwl=call :echo_time_message_write_log&:: Display and Log a Message with a Time Stamp
    :: -----------------------------------------------------------------
    set sep=-------------------------------------------------&::
    :: -----------------------------------------------------------------
       %wl%  %sep%
    :: -----------------------------------------------------------------
    :: -----------------------------------------------------------------

    :: Sample message lines:

       %etwl% Statting test script
       %etwl% All lines will be logged with a timestamp
       %etwl% This line will be displayed with a timestamp
       %ewl% This line will be displayed without a timestamp
       %wl% This line will be only logged with a timestamp

    :: Payload code goes here

    :: -----------------------------------------------------------------
    :: -----------------------------------------------------------------
    :Exit_Script
       @echo.
       >> "%Log%" echo.
       %wl%  %sep%
       >> "%Log%" echo.
       %etwl% Exiting %~nx0
    ::
       @echo.
       %etwl% Please press any key to Exit %~nx0
       >> "%Log%" echo.
       %wl%  %sep%
       >> "%Log%" echo.

    ::
       @pause > nul

    @exit&::   Exit point of the script
    :: -----------------------------------------------------------------
    ::
    :: -----------------------------------------------------------------
    :: Logging section
    ::
    :: Log (and Echo) a line starting with the Date and Time
    ::
    :: Usage:
    ::  Include the following definitions into the batch file:
    ::  set wl=  call :write_log&                  :: Log a Message with a Time Stamp
    ::  set ewl= call :echo_write_log&             :: Display a Message and Log it with a Time Stamp
    ::  set etwl=call :echo_time_message_write_log&:: Display and Log a Message with a Time Stamp
    ::  set Log=Log_File_Path_and_Name
    ::
    ::  Include the Logging Function into a batch file as:
    ::
    ::  goto :Last_Step
    ::
    ::  :: The Logging Function
    ::
    ::  :Last_Step
    ::  exit
    ::  Call the logging functions like:
    ::  %wl% message to be loged with a time stamp
    :echo_write_log
       set echo_t_m=N
       set message=%*
       @echo %message%
       goto wr_log
    :echo_time_message_write_log
       set echo_t_m=Y
       set message=%*
       goto wr_log
    :write_log
       set echo_t_m=N
       set message=%*
    :wr_log
       for /F "tokens=1* delims==" %%D IN ('date /T') do (
           for /F "tokens=2,3,4* delims==:." %%T IN ('echo/^|time') do (
               @echo %%D%%T:%%U:%%V.%%W %message% >> "%Log%"
               if /I '[%echo_t_m%]'=='[Y]' @echo %%D%%T:%%U:%%V.%%W %message%
               goto :e_log
               )
           goto :e_log
           )
    :e_log
    goto :EOF
    :: -----------------------------------------------------------------
    :: End of the Logging section
    :: -----------------------------------------------------------------

    ::

    Friday, October 14, 2011 11:54 PM