Reputation: 123
When running a batch that determines the average latency of a set of pings, I've come across an unusual problem. When piping a ping to the FIND command, if the string being searched for is not found in the ping, the the FOR loop does not execute the DO portion, but instead skips to the next iteration.
Take this command for example:
FOR /F "tokens=*" %X in ('ping 8.8.8.8 -n 1 -w 1000^|FIND "Average"') do echo Result: %X
If you toss that into a command line, you get the expected result:
Result: Minimum = 23ms, Maximum = 23ms, Average = 23ms
For a failed ping, I would expect to see: Result:
Instead the result is null. You can try this by tossing a dead IP in there.
FOR /F "tokens=*" %X in ('ping 1.1.1.1 -n 1 -w 1000^|FIND "Average"') do echo Result: %X
When using this in a batch script where the command is iterating IP addresses from a text file, this causes me all kinds of havoc.
For example, I have ip.txt which contains {8.8.8.8,8.8.4.4,1.1.1.1}. When the batch files runs in a loop, I expect the output to be:
8.8.8.8 - 23ms
8.8.8.8 - 36ms
1.1.1.1 -
8.8.8.8 - 20ms
8.8.8.8 - 18ms
1.1.1.1 -
Instead I get:
8.8.8.8 - 23ms
8.8.8.8 - 36ms
8.8.8.8 - 20ms
8.8.8.8 - 18ms
The lines that FIND returns without a value ignore the DO phase and instead iterate immediately.
I've never seen a FOR loop ignore the DO phase and keep iterating under any circumstance before. Any workarounds or even just an explanation as to why this is happening would be wonderful.
Thanks!
EDIT1: Harry Johnston
Thanks for the quick reply. Unfortunately the problem can't be solved outside the FOR loop as best I can tell.
This works fine for the single IP example, but not for multiple values being iterated. The problem is, as laid out, your example will only ever return a single line after all iterations, and that line will be the value of the last iteration.
This is much closer to the actual code that's running. Create a text file name ip.txt and fill it with a list of IP addresses, one per line.
test.bat
@echo off
cls
if not exist [logs] md [logs]
FOR /F "eol=;" %%i in (ip.txt) do for /f "tokens=9" %%p in ('ping %%i -n 3 -w 1000 ^|FIND "Average"') do echo Result - %%p
pause
ip.txt
8.8.8.8
1.1.1.1
8.8.4.4
With your solution this will only ever echo the result of [8.8.4.4]'s ping. As is it will return the first and third results, completing skipping te second.
EDIT2: dbenham
You guys are great, this worked out perfectly! Thank you again for taking the time to explain the problem as well as providing an answer :)
FOR /F "eol=;" %%i in (ip.txt) do for /f "tokens=9" %%p in ('ping %%i -n 3 -w 1000^|find "Average"^|^|echo . . . . . . . . ERROR') do echo Result: %%p
Upvotes: 2
Views: 1370
Reputation: 130839
Harry Johnston is 100% correct, the behavior you are seeing is exactly as expected. When you PING an invalid address the FIND command is not producing any output so there is nothing to iterate!
You need to modify the IN() clause so that the invalid addresses still produce output. I see 2 simple solutions:
1) Use FINDSTR instead of FIND and search for "Average" and "100% loss". If there are more than 9 tokens then it is an error situation. For this demo I embedded the IP addresses in the script.
@echo off
for %%I in (8.8.8.8 1.1.1.1) do for /f "tokens=9,10" %%A in (
'ping %%I -n 3 -w 1000^|findstr /c:"Average" /c:"100%% loss"'
) do if "%%B" neq "" (echo %%I ERROR) else echo %%I %%A
and here is some sample output:
8.8.8.8 37ms
1.1.1.1 ERROR
2) Use the ||
operator to conditionally echo a line if FIND fails
for %%I in (8.8.8.8 1.1.1.1) do for /f "tokens=9" %%A in (
'ping %%I -n 3 -w 1000^|find "Average"^|^|echo . . . . . . . . ERROR'
) do echo %%I %%A
The output is the same as for the 1st code.
It is also trivial to extend Harry's solution to support a list of IPs. Simply reset the result variable before the inner loop.
setlocal enableDelayedExpansion
for %%I in (8.8.8.8 1.1.1.1) do (
set "result=ERROR"
for /f "tokens=9" %%A in (
'ping %%I -n 3 -w 1000^|find "Average"'
) do set "result=%%A"
echo %%I !result!
)
Again, the output is the same.
Upvotes: 1
Reputation: 36318
This is expected behaviour. The DO clause is executed once for each line of output, and the FIND command only outputs lines that match the clause, so the DO clause will be executed only if at least one line matches the clause.
You could try something like this:
set result=No Response
FOR /F "tokens=*" %X in ('ping 1.1.1.1 -n 1 -w 1000^|FIND "Average"') do set result=%X
echo Result: %result%
Upvotes: 3