<

Go Premium for a chance to win a PS4. Enter to Win

x

Advanced DOS batch pitfalls

Published on
27,168 Points
13,668 Views
20 Endorsements
Last Modified:
Awarded
The following is a collection of cases for strange behaviour when using advanced techniques in DOS batch files. You should have some basic experience in batch "programming", as I'm assuming some knowledge and not further explain the basics. For some basics I will create a tutorial to be published here very soon (reference will be posted here).

It's an Interpreter
If using complex mechanisms like subshelling (using commands enclosed in round brackets), keep in mind batch files are not really like using a programming language - batch language isn't that accurate, there are several flaws in the interpreter we have to take care of.

As  cmd.exe  being an interpreter, it reads line after line, and does some pattern replacing in advance to executing the line.
A "line" can be
* a single physical line
* several physical lines connected by a caret (^) at the end of each line
* several physical lines enclosed in round brackets

A single line is hence:
 
@echo off

Open in new window


if 1 == 1 (echo yes) else (echo no)

Open in new window


echo Writing a long ^
text here

Open in new window


if 1 == 1 (
  echo yes
) else (
  echo no
)

Open in new window

Why should one know? As said already, it's an interpreter we use, and it's applying some string replacements.
If you are using environment variables, which is one of the things you will use every time, this gets most important.
E.g. the following code will not work as expected:
 
set example=1
if %example% == 1 (
  set example=2
  echo %example%
)
REM result:   1
REM expected: 2

Open in new window

In the first line, variable  example  is set to 1. In the next "line", containing  IF  up to the closing bracket, each occurance of  %example%  is replaced by the value set at that time, which is 1. That's why we almost always use Delayed Expansion. I will not further discuss that feature here; however, it might bring another headache in some (very advanced) cases:
 
setlocal EnableDelayedExpansion
set pwd=#^!pwd#
echo !pwd! %pwd%
set pwd=#^^^!pwd#
echo !pwd! %pwd%

Open in new window

Try it, and try to spot the exclamation mark ...

Escape!
That leads us to escaping. If you have to escape some control characters to override its special meaning, e.g. round brackets, pipe, percent sign, aso., you will have to escape them again for each interpreter go. Sometimes, that is. And that is the problem here, you will have to try your code ALWAYS for nested commands working as desired.
It's hard to construct examples for that, as they seem to follow no logic at all, so you will have to believe me. Working example which needs escaping is:
 
for /F "tokens=*" %%F in ('dir /a:-d . /s/b ^| find "\temp\"') do del /f %%F

Open in new window

The pipe character needed escaping, because it is used for piping the output of one command to the input of another. If not escaped,  cmd.exe  would try to parse it when reading the complete line, which leads to a syntax error because of incomplete  FOR.

Goto Issue
Coming back to variable expansion, oBdA (http://www.experts-exchange.com/M_960827.html) pointed out a strange behaviour one should be aware of. See his example (extracted from http://www.experts-exchange.com/Q_24416652.html#24410829):
 
@echo off
set Var=Value 1
if 1==1 (
  echo Var when entering the block: %Var%; setting it to "Value 2" now ...
  set Var=Value 2
  goto SomeLabel
  :SomeLabel
  echo Var is now: %Var%
)

Open in new window

This will output Var as being 2, while without  GOTO  it results in 1, the value set before the block. This is because  cmd.exe  will reinterpret the code after it executes a  GOTO. This is only a showcase, of course, noone would ever come to the conclusion having to use a goto in a block?!

The Case of the Missing Bracket
Another pitfall you might run into is accidently omitting a closing bracket. This will do NOTHING, and give you some brain work trying to debug. Example:
 
(echo Start
 for /L %%L in (1,1,100) do echo %%L
 echo End
REM --- Missing closing ")" here

Open in new window

Will not echo anything - because the line is never ended because of the missing ")", and the command is never executed ...

No Whitespace allowed!
Just another strange thing to note: If you try to break a line into two by using a caret, you can't use whitespace at the beginning of the line:
 
for %F in (*) do ^
   echo %%F

Open in new window

will give you errors that the command " " could not be found.

Block or one-liner?
Something which might puzzle you (I am puzzled at least) is the one-liner behaviour versus using blocks:
 
if 1 == 1 echo yes & echo another yes

Open in new window

This line will echo both commands. I.e. the line after  IF  is handled as one command. You could expect, as  &  is the command separator, that the  IF  is evaluated up to the ampersand, and after this is starting a new command. Wrong!
 
for %F in (*) do @echo %F > output.txt
for %F in (*) do @echo %F >> output.txt
(for %F in (*) do @echo %F) > output.txt

Open in new window

Line 1 will write the last result into  output.txt. You could expect that the stdout redirection into a file (> output.txt) would apply to the  FOR, but it's not, it is applied to the command (ECHO). And that is meaning that the file is overwritten in each go of the  FOR  loop. This also means that the file is opened and closed multiple times, which is a performance issue, so even if you use the append (>>) redirector like shown in line 2 it would be bad practice.
The third line will do what we want: collect all echo generated by the  FOR  loop, and writing it into the file all at once.

Redirecting output
Talking about redirection: Funny applications are
 
>> output.txt  echo yes
>> output.txt  if 1 == 1 echo This is a syntax error
>> output.txt (if 1 == 1 echo yes, three)
>> output.txt  dir c:\* | findstr MyFiles
>> output.txt (dir c:\* | findstr MyFiles)

Open in new window

The first and third line actually work, the echoed text is appended to  output.txt. After understanding the first line, the second seems to be logically correct - but instead, a syntax error is generated, stating  IF  is not expected at that position.
After adding brackets, as in third line, everything is fine.
Forth line doesn't what we want. DIR  output is stored in the file, and  FINDSTR  is acting on a empty pipe. Hence the fifth line has to be used.
As a rule of thumb, if you want to use above syntax: Complex commands, like  IF  and  FOR, which could or actually do require round brackets as part of their syntax, need to be enclosed in round brackets. Command chains (|, &, &&, ||) have to be enclosed, too.

The following syntax is not recommended, even if working as desired - it is obscuring the meaning and hard to read:
 
dir c:\* | >> output.txt findstr MyFiles

Open in new window



Very handy for dynamically generated scripts, e.g. for FTP, is this syntax
 
@echo off
call :genscript > script.cmd
call script.cmd
exit /b

:genscript
echo @echo off
echo dir
exit /b

Open in new window


But be aware that piping does not work. If we change above call to
 
call :genscript | findstr dir

Open in new window

we get the same error as if we would try to use that call on commandline. I did not find any workaround to get this running yet.


This article is subject to occasional changes. I'll post a short comment about each addition.
20
Comment
Author:Qlemo
  • 6
  • 2
  • 2
  • +4
14 Comments
 
LVL 5

Expert Comment

by:Cumbrowski
Adding to the examples the explanation for "What is expected?" would help. Even though I consider myself not to be a beginner in MS DOS batch, had some problems with following you, because I had to spend time figuring out what the intention is for each example.

For example:

You wrote:

<quote>
set example=1
if %example% == 1 (
  set example=2
  echo %example%
)

will not work as expected.
<end quote>

Better would have been

set example=1
if %example% == 1 (
  set example=2
  echo %example%
)

The expected result of this code is the output

2

The actual output of the code is

1

...


0
 
LVL 71

Author Comment

by:Qlemo
Thanks, Cumbrowski, for the comment. I reworked the article to include the expected results.
0
 
LVL 5

Expert Comment

by:Cumbrowski
It looks much better. How did you format the code snippets within your article? Is that how it looks, if you are using the [code] BB tag?
0
Veeam Disaster Recovery in Microsoft Azure

Veeam PN for Microsoft Azure is a FREE solution designed to simplify and automate the setup of a DR site in Microsoft Azure using lightweight software-defined networking. It reduces the complexity of VPN deployments and is designed for businesses of ALL sizes.

 
LVL 71

Author Comment

by:Qlemo
Yes, the code snippets are all formatted with using [code]. There have been some (documented) flaws with it, regarding trailing blanks, but they seem to have worked on that.
0
 
LVL 71

Author Comment

by:Qlemo
Added call syntax in redirection chapter
0
 
LVL 60

Expert Comment

by:Kevin Cross
Nice collection, Qlemo.  Thanks for the article!

Voted yes above.
0
 
LVL 14

Expert Comment

by:Ben Personick (Previously QCubed)
Hey Qlemo this is a very nice article!

I'd just like to point out that you can do this to get the output you desire without using delayed expansion, I wrote a few examples and posted it below.

  I hope I'm not duplicating your work or if the best place to cover this is here or in a different article, I've only read this one so I'm not sure if there is a better place to post this info.

 
ECHO off

SET example=1
IF %example% == 1 (
  SET example=2
  ECHO %example%
  SET example=3
  ECHO %example%
)

ECHO Expected Results: 2,3
ECHO Actual Results:   1,1


SET example=1
IF %example% == 1 (
  SET example=2
  CALL ECHO %%example%%
  SET example=3
  CALL ECHO %%example%%
)
ECHO Expected Results: 2,3
ECHO Actual Results:   2,3


SET "Example2=0"
FOR /L %%a IN (1,1,3) DO (
SET /A "Example2=%Example2%+%%a"
ECHO %Example2%
)
ECHO %Example2%

ECHO Expected Results: 1,3,6,6
ECHO Actual Results:   0,0,0,3

SET "Example2=0"
FOR /L %%a IN (1,1,3) DO (
CALL SET /A "Example2=%%Example2%%+%%a"
CALL ECHO %%Example2%%
)
ECHO %Example2%

ECHO Expected Results: 1,3,6,6
ECHO Actual Results:   1,3,6,6

ECHO To further illistrate what happens:

SET "Example2=0"
FOR /L %%a IN (1,1,3) DO (
CALL SET /A "Example2=%%Example2%%+%%a"
ECHO a = %%a
ECHO Example2 = %Example2%
CALL ECHO Escaped Example2 = %%Example2%%
)
ECHO Final value = %Example2%

Open in new window

0
 
LVL 33

Expert Comment

by:knightEknight
Here's one that took me a while to trace down ... note the punctuation in the mystr variable, and the effect that setlocal has on it:

@echo off
 echo.
 echo The Exclamation....

 call :label   Now you see it!

 setlocal enabledelayedexpansion

 call :label   Now you don't!

 exit/b


:label
 set mystr=%*
 echo.
 echo %mystr%  (!mystr!)
 exit/b

Open in new window

0
 
LVL 71

Author Comment

by:Qlemo
Sorry, both times I get the same output - no exclamation. That is because I have Delayed Expansion switched on by default :P

Interesting, because you can't get the exclamation mark displayed, even when escaped with n carets (try it - using
 call :label   Now you don't^^^^!
gives funny results).
0
 
LVL 33

Expert Comment

by:knightEknight
I also experimented with the ^ escapes and found the same thing.

I'm curious if you can turn off Delayed Expansion (if it is on by default) with endlocal:

  endlocal
  call :label  Now you see it!

This will work if setlocal enabledelayedexpansion is explicitly set earlier in the script, but I don't know if it will work if expansion is on by default.
0
 
LVL 71

Author Comment

by:Qlemo
No, it would not help. You need to use setlocal DisableDelayedExpansion, or call a    cmd /v:off
0
 
LVL 41

Expert Comment

by:footech
Minor correction - In the "Block or one-liner?" section, there is a reference to "the third line", which I'm assuming is referencing the second line.

Anyway, good article.
0
 
LVL 71

Author Comment

by:Qlemo
Thanks, footech. I have introduced a new line 2 now, so the reference is correct ;-).
0
 
LVL 11

Expert Comment

by:loftyworm
very nice, going in my DOS batch links library!
0

Featured Post

New feature and membership benefit!

New feature! Upgrade and increase expert visibility of your issues with Priority Questions.

Join & Write a Comment

This tutorial will teach you the core code needed to finalize the addition of a watermark to your image. The viewer will use a small PHP class to learn and create a watermark.
In this fifth video of the Xpdf series, we discuss and demonstrate the PDFdetach utility, which is able to list and, more importantly, extract attachments that are embedded in PDF files. It does this via a command line interface, making it suitable …
Suggested Courses

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month