DBZHOU
DBZHOU

Reputation: 177

Batch script: SET command behaves differently for the similar code piece

I have been using batch for a while but not an expert of it.
I recently wrote a piece of code to do the following thing:

Get a date input from the command line. If its format is like 20200102, it is converted to 2020-01-02 and get its previous date 2020-01-01.

So I wrote the following code and save it in test.bat:

set output_file_date=%~1
set run_date="%output_file_date%"
set output_file_date=%output_file_date_PARAM%
set run_date=%output_file_date:~0,4%-%output_file_date:~4,2%-%output_file_date:~6,2%
FOR /f "usebackq" %%i IN (`PowerShell ^(Get-Date^('%run_date%'^)^).AddDays^(-1^).ToString^('yyyy-MM-dd'^)`) DO SET run_start_date=%%i
ECHO change the date format to "%run_start_date%"
pause

The output on running this batch file from within a command prompt window is:

D:\>test.bat "20250204"
D:\>set output_file_date=20250204
D:\>set run_date="20250204"
D:\>set output_file_date=20250204
D:\>set run_date=2025-02-04
D:\>FOR /F "usebackq" %i IN (`PowerShell (Get-Date('2025-02-04')).AddDays(-1).ToString('yyyy-MM-dd')`) DO SET run_start_date=%i
D:\>SET run_start_date=2025-02-03
D:\>ECHO change the date format to "2025-02-03"
change the date format to "2025-02-03"
D:\>pause
Press any key to continue . . .

As you can see, the code worked as expected.

Then I moved the above code piece to a batch file with a little bit modification. I put it in a procedure.

Some other codes ahead
...
echo "%output_file_date%"
set run_date="%output_file_date%"
set check=%output_file_date:~4,1%
echo "%check%"
IF "%check%" NEQ "-" (
    set run_date=%output_file_date:~0,4%-%output_file_date:~4,2%-%output_file_date:~6,2%
    echo "%run_date%"
    FOR /f "usebackq" %%i IN (`PowerShell ^(Get-Date^('%run_date%'^)^).AddDays^(-1^).ToString^('yyyy-MM-dd'^)`) DO SET run_start_date=%%i
    ECHO change the date format to "%run_start_date%"
    ) 

Then the code piece stopped working. The output became as:

D:\>complex.bat "20250203"
"20250203"
""
""
change the date format to ""

I also removed @echo off at the top, the output of this piece of code is as following.

echo "20250203"
set run_date="20250203"
set check=0
echo ""
IF "" NEQ "-" (
  set run_date=2025-02-03
  echo ""
  FOR /F "usebackq" %i IN (`PowerShell (Get- 
  Date('')).AddDays(-1).ToString('yyyy-MM-dd')`) DO SET 
  run_start_date=%i
  ECHO change the date format to ""
)

Does anyone know what was happening here? It seems the set stopped working? How can I fix this?

Upvotes: -1

Views: 69

Answers (2)

Compo
Compo

Reputation: 38708

As it is so far unmentioned, there is no issue with the SET command behaving differently, because the output from test.bat "20250204" you submitted is not a true reflection of what happens.

So let's start with the actual output from test.bat:

D:\>test.bat "20250204"

D:\>set output_file_date=20250204

D:\>set run_date="20250204"

D:\>set output_file_date=

D:\>set run_date=~0,4output_file_date:~4,2output_file_date:~6,2

D:\>FOR /F "usebackq" %i IN (`PowerShell (Get-Date('~0 4output_file_date:~4 2output_file_date:~6 2')).AddDays(-1).ToString('yyyy-MM-dd')`) DO SET run_start_date=%i

D:\>SET run_start_date=Get-Date

D:\>SET run_start_date="System.DateTime".

D:\>SET run_start_date=At

D:\>SET run_start_date=+

D:\>SET run_start_date=+

D:\>SET run_start_date=+

D:\>SET run_start_date=+

D:\>ECHO change the date format to "+"
change the date format to "+"

D:\>pause
Press any key to continue . . .

As you can clearly see, your issue is that there is no variable defined with the name output_file_date_PARAM, and therefore what you are inevitably passing to PowerShell, as %run_date%, is not a date string, i.e. ~0,4output_file_date:~4,2output_file_date:~6,2!


Now let's move onto your second example output, (we have to assume that you once again used set output_file_date=%~1 within Some other codes ahead):

D:\>complex.bat "20250203"

D:\>echo "20250203"
"20250203"

D:\>set run_date="20250203"

D:\>set check=0

D:\>echo "0"
"0"

D:\>IF "0" NEQ "-" (
set run_date=2025-02-03
 echo ""20250203""
 FOR /F "usebackq" %i IN (`PowerShell (Get-Date('"20250203"')).AddDays(-1).ToString('yyyy-MM-dd')`) DO SET run_start_date=%i
 ECHO change the date format to "+"
)
""20250203""

D:\>SET run_start_date=Get-Date

D:\>SET run_start_date=not

D:\>SET run_start_date=At

D:\>SET run_start_date=+

D:\>SET run_start_date=+

D:\>SET run_start_date=+

D:\>SET run_start_date=+
change the date format to "+"

This clearly does not match what you have shown us as the output from complex.bat "20250203", given your complex.bat content.

In summary, nothing you have submitted reflects a mcve, and does not show a command behaving differently with the SET command.


As a courtesy, here's what your complex.bat code snippet should have looked like:

Echo "%output_file_date%"
Set "run_date=%output_file_date%"
Set "check=%output_file_date:~4,1%"
Echo "%check%"
If Not "%check%" == "-" (
    Set "run_date=%output_file_date:~0,4%-%output_file_date:~4,2%-%output_file_date:~6,2%"
    SetLocal EnableDelayedExpansion
    Echo "!run_date!"
    For /F %%G In ('%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe -NoProfile -Command "(Get-Date('!run_date!')).AddDays(-1).ToString('yyyy-MM-dd')"') Do Set "run_start_date=%%G"
    Echo change the date format to "!run_start_date!"
    EndLocal
)

I have added this because you need to understand that any variable modified within a parenthesized code block requires expanding not at block read time, (the default), but at run time, i.e. delayed.

Expected output, (in comparison to your submission):

D:\>complex.bat "20250203"
"20250203"
"0"
"2025-02-03"
change the date format to "2025-02-02"

Delaying variable expansion is one of the most common problems reported on this site under the tag, and as such also renders your question an off topic duplicate.

Upvotes: 0

Magoo
Magoo

Reputation: 80173

When a batch file starts, its errorstate is ON. This means that any command read from the batch file is first displayed (echoed) and then executed.

Consequently, if the first command is echo off then that line is displayed, then executed - so echo off would be displayed and the remainder of the commands NOT displayed before execution as the echostate has now been turned OFF.

Any command preceded by @ will NOT be displayed before execution, so @echo off as the first line of a batch will NOT display the command , then turn echostate to OFF.

setlocal causes a new subshell to be entered. The parameters applied to the setlocal command sets the delayedexansion and extensions mode of the subshell and the subshell acquires its own copy of the then-current environment.

When an endlocal command is reached, then the new environment is discarded and the original environment is restored, hence variables created, deleted or altered in the subshell will disappear, reappear or revert to their original value.

A quirk of how the language is parsed means that the command endlocal&set "varname=%varname%" will allow a new variable value to be exported from the setlocal/endlocal "bracket".

& simply separates commands that will be executed consecutively.

I always use

@echo off
setlocal

since I often turn echo on for debugging. It's easier for me to move to the top of the file, then the end of the line to alter off to on or vice-versa, instead of having to move the cursor over either the @echo o or the &setlocal…

Upvotes: 2

Related Questions