Strange issue with FOR loop

May 26, 2008
492
3
#1
I get unexpected results with this batch file:

Code:
@echo off

for %%a in (
123
234
345
456
) do (
grep %%a test.txt
)
TC doesn't run the grep command correctly, and then it also executes test.txt after each grep command, opening the file in Notepad. If I restructure it to look like this, everything works correctly:

Code:
@echo off

for %%a in (123 234 345 456) do (
grep %%a test.txt
)
CMD behaves correctly with both layouts. It seems TC has an issue when there are line breaks in the set.

I confirmed this on both 15.01.52 and 15.01.54.
 
May 26, 2008
492
3
#3
I've used this syntax (with CMD) sometimes since XP, if not possibly Win2k. Surprisingly I must not have tried it in TCC until today.

I find it very convenient when I'm copying a list of something (server names, for instance) from Excel or some other location where there is one entry per line. With TCC I would be forced to join all the lines and put spaces between the entries. It would be great if TCC would support this CMD syntax.
 
May 26, 2008
492
3
#6
I've not tried looking to see if it's documented, but CMD has supported the syntax since at least NT 4.0. I can try doing some searching but it may be an undocumented feature.
 
#7
Rex: it isn't documented that I know of (cmd.exe's "documentation" is strictly from hunger, at best).

The bit with the parens is just one of those things you stumble across and hack around with until it kinda sorta works, except when it doesn't.

I don't have any flavor of grep.exe here. For less sophisticated searches, when I don't want to fire up Perl, I make do with findstr.exe -- and this works ok (no sign of 'test.txt' trying to open itself in Notepad):

do string in /L 123 234 345 456
findstr.exe /i /m /c:"%string" test.txt
iff errorlevel == 0 then

:: do something here
else
:: do something else
endiff
enddo

I dispensed with 'for' a long time ago on realizing how much better 'do' is in so many ways. Egad, being able to dispense with 'for' and use 'do' is, IMO, one of the (many) huge "draws" of the whole 4NT/TCC system!

Though the example in the first message, above, does work in cmd.exe, that "command processor" has always had unpleasant bugs w.r.t. parens. Nest enough of 'em, and eventually you run across the bugs. All praise to 'do'...
 
#8
Here's an example of a truly fun obscure bug in cmd.exe that pertains to using parens in loops:

I always use "::" to begin comments. It's just that much less mess on the screen, compared with "REM". So one day at work, years ago, a co-worker had a batch file that kept failing miserably. She'd decided -- on my recommendation -- to make liberal use of the "::" alternative to "REM". She'd written a "for" loop that should have worked just fine. But: she started getting these miserable failures. The batch file was hardly rocket-science! Nobody could figure out why the thing didn't work. Then it occurred to me -- as stupid as it sounded:
there were a number of nested parens in this "for" loop -- and multiple lines. What if the "::" comments were the problem? YEP. When I added "REM" in place of "::", the stupid little batch file started working. Totally lucky guess. The other fun part? It didn't always fail with "::" inside the loop and inside the parens. It only failed sometimes.

How do I hate thee, cmd.exe? Let me count the ways...
 
May 26, 2008
492
3
#9
I dispensed with 'for' a long time ago on realizing how much better 'do' is in so many ways. Egad, being able to dispense with 'for' and use 'do' is, IMO, one of the (many) huge "draws" of the whole 4NT/TCC system!
I hear what you're saying, but I work on soooo many systems that I often use CMD and syntax that works well with it. I was simply surprised that TCC did not support this syntax that CMD works just fine with.
 
#10
Just because undocumented syntax seems to do what you expect on some systems, you cannot depend on it - you need to verify it after each OS update that it still behaves the same way (alternately, you can verify that neither CMD.EXE nor any API it uses was changed by the update...). Of course, if you use multiple systems, you need to test each one for its conformance to your expectations, which go beyond what its vendor claims...
 
#11
> CMD behaves correctly with both layouts. It seems TC has an issue when there are line breaks in the set.

You can at least cut your losses by getting rid of the line breaks and putting the set of strings into a single line. If there are so many of them that they make the .cmd file difficult to read at that point, consider using more than one "for" loop.

I came to so distrust cmd.exe, with its quirks w.r.t. parens and its damnable delayed-variable-expansion silliness, that except for the simplest possible kinds of "for" routines I took to doing it this way:

for %%A in (whatever goes here) do call :Some_Label %%A

In non-trivial batch files in which I'd MUCH rather use TCC syntax if at all possible: right at the top, a test -- is TCC running? If so, forget cmd.exe syntax entirely:

if "%_cwps%" NEQ "" goto Use_TCC

... a TCC-only version within the .cmd file. Or, an entirely separate .btm file that gets launched at that point.

If it's really better to have the "set" in question arranged one-item-per-line, you could ECHO the strings >> to a temporary text file and use "for /f" on the file. In any event, Steve Fabian is right that the undocumented behavior could change the next time there's an o.s. update. Then it seems to me the only way you'd find out would be if a bunch of your .cmd files started breaking. That'd be an unpleasant surprise...
 
May 26, 2008
492
3
#12
Just because undocumented syntax seems to do what you expect on some systems, you cannot depend on it - you need to verify it after each OS update that it still behaves the same way (alternately, you can verify that neither CMD.EXE nor any API it uses was changed by the update...). Of course, if you use multiple systems, you need to test each one for its conformance to your expectations, which go beyond what its vendor claims...
I did verify it works as I expected all the way from NT 4.0 to Windows 2012. Seems pretty consistent to me!
 
May 26, 2008
492
3
#13
You can at least cut your losses by getting rid of the line breaks and putting the set of strings into a single line. If there are so many of them that they make the .cmd file difficult to read at that point, consider using more than one "for" loop.
That's just it, sometimes I may have a couple hundred strings so joining them together in a single line would not work. Making multiple FOR loops is just a lame workaround IMO. I could put all the lines in a separate file and just reference the file itself in the FOR loop, but then I have two files instead of one.

In reality I'm just going to end up using CMD in cases like this instead of TCC.
 
#14
Escaping the newlines will give the readability you want (but won't be CMD-compatible).
Code:
v:\> type ml.bat
@echo off
for %%a in (^
123 ^
234 ^
345 ^
456 ^
) do (
echo %%a test.txt
)

v:\> ml.bat
123 test.txt
234 test.txt
345 test.txt
456 test.txt
 
#15
> That's just it, sometimes I may have a couple hundred strings so joining them together in a single line would not work.

Probably not. Do you hard-code the batch files with the specific lists of strings?

> I could put all the lines in a separate file and just reference the file itself in the FOR loop, but then I have two files instead of one.

This never struck me as a terrible hardship, especially if I could create one (non-hard-coded) batch file and just create the input file on the fly, as need be. In which case, we're talking about two files only at the time the first batch file is created, plus the input file. After that, the batch file remains unchanged and you don't have to think about it again, right? Only the input file -- updated as need be on the fly -- has to change. If the list of strings doesn't have to be made by hand but can be generated within the batch file itself (no manual intervention by you, that is), then really there's only one file to consider -- in practical terms -- since you can have the batch file generate a temporary file and remove it when you're done. I don't mind if the solution looks a bit lame to someone else -- if I can keep it TCC-ish and not be forced to use .cmd syntax. Cmd.exe batch files might have improved marginally over the years, but it still strikes me that .btm files execute somewhat faster. So: hugely better command-set, faster execution... TCC 1, cmd.exe 0. But that's just me. : )