DO ... | ... (pipe ends prematurely)

May 20, 2008
9,351
62
Syracuse, NY, USA
... at least that's what I think is happening. I have used this construction before, with no problems. Below, sometimes I see 1 line, sometimes none, but never what I expect to see. C:\foobar does not exist

Code:
v:\> type pipetest.btm
on error iterate
do i=1 to 5 | ffind /v /k /m /e"."
        echo %i
        iff %i == 3 then
                dir c:\foobar
        endiff
enddo

v:\> pipetest.btm

v:\> pipetest.btm
1

v:\> pipetest.btm
1

v:\>
 

rconn

Administrator
Staff member
May 14, 2008
10,988
98
WAD. I'm not sure what you're trying to do here, but what you're actually doing is asking TCC to pipe the output of the DO variable assignment. It's a little surprising that you get anything from the loop, but that's a matter of timing.

If what you really want is to redirect the output of everything in the DO loop, you need to either:

1) Use a single-line DO construct
2) Put the DO loop in a second batch file and call that (i.e., "dotest.btm | ffind ...")
2) Put the DO loop in a library function
 
May 20, 2008
9,351
62
Syracuse, NY, USA
Funny! It works in quite complicated cases as long as there's no error. My example was a simplification of this (below) which queries 13 NTP servers and prints the estimates of the local time offset sorted by that oddset. When all 13 servers reply I see the individual results. If any query fails I don't.

Code:
v:\> type timecheck.btm
setlocal
set sum=0
set hosts=0
set max=-100
set min=100
setarray /f z[3]

on error iterate

do host in @\\zz\v$\servers.txt | g:\gnu\sort -n -k 2
        noop %@execarray[z,timesync /n %@word[1,%host]]
        set rtt=%@word[4,%z[0]]
        set offset=%@word[4,%z[2]]
        echo %@word[0,%host]^t%offset^t(%rtt)
        set /a sum+=%offset
        if %offset gt %max set max=%offset
        if %offset lt %min set min=%offset
        set /a hosts+=1
enddo
on error
echo ^r^n%hosts hosts responding, min = %min, max = %max
set sum=%@eval[%sum - %min - %max]
set average=%@eval[%sum / (%hosts - 2)=3]
echo adjusted average: ^e[32;1m%@if[%average GT 0,+,]%average^e[0m (%_time %_dow %_date)
unsetarray z

v:\> timecheck.btm
time-b-g.nist.gov       -0.027  (31)
time-c-g.nist.gov       -0.025  (20)
time-a-b.nist.gov       -0.024  (59)
time-a-g.nist.gov       -0.024  (17)
time-a-wwv.nist.gov     -0.024  (58)
time-b-b.nist.gov       -0.024  (68)
ntp0.cornell.edu        -0.023  (22)
time-b-wwv.nist.gov     -0.023  (56)
time-d-g.nist.gov       -0.023  (24)
time-d-wwv.nist.gov     -0.023  (57)
time-c-b.nist.gov       -0.021  (63)
time-d-b.nist.gov       -0.021  (64)
time-c-wwv.nist.gov     -0.020  (63)

13 hosts responding, min = -0.027, max = -0.020
adjusted average: -0.023 (22:41:49 Sun 2018-12-23)

v:\> timecheck.btm

12 hosts responding, min = -0.173, max = -0.021
adjusted average: -0.025 (22:43:41 Sun 2018-12-23)
 
May 20, 2008
9,351
62
Syracuse, NY, USA
2) Put the DO loop in a second batch file and call that (i.e., "dotest.btm | ffind ...")
How?
Code:
call tchelper.btm | g:\gnu\sort -n -k 2
has the same problem. If there's an error (non-responding host) the pipe outputs nothing.

Code:
v:\> type \\bb\v$\timecheck.btm
setlocal
set sum=0
set hosts=0
set max=-100
set min=100
setarray /f z[3]

call \\bb\v$\tchelper.btm | g:\gnu\sort -n -k 2

echo ^r^n%hosts hosts responding, min = %min, max = %max
set sum=%@eval[%sum - %min - %max]
set average=%@eval[%sum / (%hosts - 2)=3]
echo adjusted average: ^e[32;1m%@if[%average GT 0,+,]%average^e[0m (%_time %_dow %_date)
unsetarray z

v:\> type \\bb\v$\tchelper.btm
on error iterate
do host in @\\zz\v$\servers.txt
        noop %@execarray[z,timesync /n %@word[1,%host]]
        set rtt=%@word[4,%z[0]]
        set offset=%@word[4,%z[2]]
        echo %@word[0,%host]^t%offset^t(%rtt)
        set /a sum+=%offset
        if %offset gt %max set max=%offset
        if %offset lt %min set min=%offset
        set /a hosts+=1
enddo
on error

v:\>
 
May 20, 2008
9,351
62
Syracuse, NY, USA
And the same thing happens if it's in a library, error ... no output from pipe.
Code:
v:\> type timecheck.btm
setlocal
set sum=0
set hosts=0
set max=-100
set min=100
setarray /f z[3]

queryntp | g:\gnu\sort -n -k 2

echo ^r^n%hosts hosts responding, min = %min, max = %max
set sum=%@eval[%sum - %min - %max]
set average=%@eval[%sum / (%hosts - 2)=3]
echo adjusted average: ^e[32;1m%@if[%average GT 0,+,]%average^e[0m (%_time %_dow %_date)
unsetarray z

if "%1" == "/s" echo Shutdown: %_date %_time Offset: %@if[%average GT 0,+,]%average >> c:\apps\workplace\shutdowntime.txt

v:\> library /f queryntp
queryntp {
on error iterate
do host in @\\zz\v$\servers.txt
noop %@execarray[z,timesync /n %@word[1,%host]]
set rtt=%@word[4,%z[0]]
set offset=%@word[4,%z[2]]
echo %@word[0,%host]^t%offset^t(%rtt)
set /a sum+=%offset
if %offset gt %max set max=%offset
if %offset lt %min set min=%offset
set /a hosts+=1
enddo
on error
}
Code:
v:\> timecheck.btm
time-a-g.nist.gov       -0.029  (20)
time-b-b.nist.gov       -0.029  (61)
time-c-g.nist.gov       -0.029  (30)
time-d-wwv.nist.gov     -0.029  (60)
ntp0.cornell.edu        -0.028  (26)
time-c-b.nist.gov       -0.028  (71)
time-a-b.nist.gov       -0.027  (68)
time-a-wwv.nist.gov     -0.027  (57)
time-b-g.nist.gov       -0.027  (29)
time-c-wwv.nist.gov     -0.027  (57)
time-d-g.nist.gov       -0.025  (27)
time-d-b.nist.gov       -0.024  (63)
time-b-wwv.nist.gov     -0.022  (61)

13 hosts responding, min = -0.029, max = -0.022
adjusted average: -0.027 (23:30:33 Sun 2018-12-23)

v:\> timecheck.btm

12 hosts responding, min = -0.176, max = -0.022
adjusted average: -0.027 (23:30:37 Sun 2018-12-23)
 
May 20, 2008
9,351
62
Syracuse, NY, USA
Here's a simplified version using CALL.
Code:
v:\> type batch1.btm
call batch2.btm %1 | ffind /v /m /k /e"."

v:\> type batch2.btm
on error iterate
do i=5 to 1 by -1
        echo %i
        iff %i == 3 .and. "%1" == "/debug" then
                beep
                dir c:\foobar
        endiff
enddo
on error

v:\> batch1.btm

5
4
3
2
1

v:\> batch1.btm /debug


v:\>
 
May 20, 2008
9,351
62
Syracuse, NY, USA
And here's a simplified version using a library function.

Code:
v:\> type batch1.btm
testlib %1 | ffind /v /m /k /e"."

v:\> library /f testlib
testlib {
on error iterate
do i=5 to 1 by -1
echo %i
iff %i == 3 .and. "%1" == "/debug" then
beep
dir c:\foobar
endiff
enddo
on error
}


v:\> batch1.btm

5
4
3
2
1

v:\> batch1.btm /debug


v:\>
 
May 20, 2008
9,351
62
Syracuse, NY, USA
And here's yet another example showing that after these bad things happen, TCC is in a state of ... disrepair (shall we say).

And (NEWSFLASH!!!) if I remove "on error iterate" it all works well. Changing it to "on error echo foo" doesn't make things better.

Code:
v:\> type batch1.btm
iff "%1" == "/debug" then
        call batch2.btm %1 | ffind /v /m /k /e"."
else
        call batch2.btm %1
endiff
v:\> type batch2.btm
on error iterate
do i=5 to 1 by -1
        echo %i
        iff %i == 3 .and. "%1" == "/debug" then
                beep
                dir c:\foobar
        endiff
enddo
on error

v:\> batch2.btm
5
4
3
2
1

v:\> batch1.btm
5
4
3
2
1

v:\> batch1.btm /debug


v:\> batch2.btm
5

v:\>
 
May 20, 2008
9,351
62
Syracuse, NY, USA
Remember that child pipe processes inherit the batch file settings, including any ON xxx commands.
It doesn't look like that here. I do this.
Code:
on error iterate
call tchelper.btm | g:\gnu\sort -g -k 2
Tchelper.btm does this:
Code:
do host in @\\zz\v$\servers.txt
    noop %@execarray[z,timesync /n %@word[1,%host]]
    set rtt=%@word[4,%z[0]]
    set offset=%@word[4,%z[2]]
    echo %@word[0,%host]^t%offset^t(%rtt)
    set /a sum+=%offset
    if %offset gt %max set max=%offset
    if %offset lt %min set min=%offset
    set /a hosts+=1
enddo
And I see this:
Code:
TCC: (Sys) V:\tchelper.btm [3]  A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond.
time-a-wwv.nist.gov             ()
time-b-wwv.nist.gov     -0.148  (366)
time-c-wwv.nist.gov     -0.003  (63)
ntp0.cornell.edu        -0.002  (23)
time-a-g.nist.gov       -0.002  (24)
time-c-b.nist.gov       +0.001  (58)
time-b-g.nist.gov       +0.002  (24)
time-c-g.nist.gov       +0.002  (27)
time-b-b.nist.gov       +0.003  (65)
time-d-b.nist.gov       +0.003  (55)
time-a-b.nist.gov       +0.004  (63)
time-d-g.nist.gov       +0.005  (31)
time-d-wwv.nist.gov     +0.006  (62)
The "ON ERROR ITERATE" doesn't seem to work (1. I see the error and 2. it doesn't iterate).

The pipe did complete (correctly), however, unlike last night.

The error message is produced by TCError() and the TIMESYNC plugin returns TCError()'s return value (2)
 
May 20, 2008
9,351
62
Syracuse, NY, USA
Here's a smaller example showing the same thing. The error message isn't suppressed and ITERATE doesn't happen.
Code:
v:\> type batch1.btm
on error iterate
call batch2.btm %1 | sort


v:\> type batch2.btm
do i=1 to 5
        iff %i == 3 then
                dir c:\foobar
        endiff
        echo %i
enddo

v:\> batch1.btm
TCC: (Sys) V:\batch2.btm [3]  The system cannot find the file specified.
 "C:\foobar"

                   0 bytes in 0 files and 0 dirs
     718,068,355,072 bytes free
 Volume in drive C is Windows      Serial number is 3ed6:5d0d
1
2
3
4
5
 
May 20, 2008
9,351
62
Syracuse, NY, USA
You cannot use an "iterate" in an ON ERROR statement - you're jumping out of the DO loop with the "on error iterate", and then you're expecting TCC to jump back into the DO loop. It won't. An "ON ..." is like a non-local GOTO.
Is there a generic workaround?

This thread has raised a few questions.

First ... I often want to evaluate a variable function and not do anything with the result. The common way is to
Code:
echo %@FUNCTION[] > NUL
.
I have a plugin just for such cases (and to help in timing things).
Code:
noop %@FUNCTION[]
Suppose I want to
Code:
 noop %@execarray[array_name,internal_command]
and later test the return value of internal_command. _? doesn't work because it will be the return value of noop (or echo). I could
Code:
noop %@execarray[array_name,internal_command & set result=%_?
and later test %result. Is there a more elegant way?

Second ... Earlier in this thread you pooh-pooh'd the construction
Code:
do ... (command... ) | command
    commands
enddo
In https://jpsoft.com/forums/threads/speaking-of-do.569/post-2690 you said "It's perfectly legitimate". I have used it since then (2008) and it seems to work. Is it kosher or not?

Third ... if the array a already has data in it and I use @EXECARRAY[a, command] and command fails, with no stdout. Is a[0] guaranteed to be an empty string?
 
May 20, 2008
9,351
62
Syracuse, NY, USA
You cannot use an "iterate" in an ON ERROR statement - you're jumping out of the DO loop with the "on error iterate", and then you're expecting TCC to jump back into the DO loop. It won't. An "ON ..." is like a non-local GOTO.
Actually it does work (I have often done it) as long as DO (or a CALLed BTM) isn't being piped.
Code:
\\bb\v$> type batch2.btm
on error iterate
do i=5 to 1 by -1
        iff %i == 3 then
                dir c:\foobar
        endiff
        echo %i
enddo

\\bb\v$> batch2.btm
5
4

 Volume in drive C is unlabeled    Serial number is b813:1d21
2
1

\\bb\v$>
 
May 20, 2008
9,351
62
Syracuse, NY, USA
Never mind my first question (2 posts ago). I found out by experiment (then read the help) that @EXECARRAY returns the return value of the command being exec'd. That's nice!
 
May 20, 2008
9,351
62
Syracuse, NY, USA
It doesn't seem to have anything to do with ITERATE. It's the combination of "ON ERROR", DO, and being piped.
Code:
v:\> type batch2.btm
on error (echo ERROR!!!!!)
do i=3 to 1 by -1
        iff %i == 2 then
                dir c:\foobar
        endiff
        echo %i
enddo

v:\> batch2.btm
3

 Volume in drive C is Windows      Serial number is 3ed6:5d0d
ERROR!!!!!
2
1

v:\> batch2.btm | sort

v:\> batch2.btm | sort
3

v:\>
 
May 20, 2008
9,351
62
Syracuse, NY, USA
Remember that child pipe processes inherit the batch file settings, including any ON xxx commands.
That seems contrary to what the help says.
An ON statement only affects the current batch file. When the batch file containing ON is exited for any reason, whether temporarily (e.g., by a CALL to another batch file) or permanently, the TCC default break and error handlers become effective. A CALLed batch file may then use ON to define its own handlers. When control returns to the calling batch file, its break and error handlers that had been in effect at the CALL are reactivated.