Monday, June 25, 2007

Hidden Danger of Pipes

OK, this is my first post, so I figure I need to explain a bit what I intend this blog is about. Batch. That's what I intend to write. Specifically, tips, tricks and pitfalls of windows batch file.

Now that we have that out of the way, let's take a look at the first of such batch quirk.

When you are using pipes to do some chained processing, just remember that the exit code of the last command in the pipe is the one that prevails.
I've seen this bitten a lot of people:

test.bat ¦ tee.exe outputfile.log

exit /b %errorlevel%

The intention here is to exit with whatever the exit code of test.bat is, when in fact you're capturing exit code of tee.exe, which will most likely be zero.
You'll then end up masking the test.bat exit code. If this is in some pre-checkin test script, that usually ends up with bad codes sneaking in.
So save yourself some time, and output to a file instead, and post process the output.

test.bat > outputfile.log

set EXITCODE =%errorlevel%

type outputfile.log

This captures your intent just the same (well, almost the same).

3 comments:

我是ㄞㄟㄣ said...

Glad to find this article.

I have a batch which invokes "make" 3 times piping the result to a "tee"-like program. I wanted it to stop when one "make" encounters error. So I used "if ERRORLEVEL" to check, but the check failed because ERRORLEVEL was masked by the "tee"-like program.
Like this: (I use WinXP pro)

make -f 1.mak | display_and_write_to a1.log
if ERRORLEVEL 1 got :EOF
make -f 2.mak | display_and_write_to a2.log
if ERRORLEVEL 1 got :EOF
make -f 3.mak | display_and_write_to a3.log
:EOF

I tried to put "make" to a second batch file like this:

REM --- main batch starts ---
set RESULT=0
call 2nd.cmd 1 | display_and_write_to a1.log
if %RESULT% GTR 0 goto :EOF
call 2nd.cmd 2 | display_and_write_to a2.log
if %RESULT% GTR 0 goto :EOF
call 2nd.cmd 3 | display_and_write_to a3.log
:EOF
REM --- main batch ends ---

REM --- 2nd batch starts ---
make -f %1.mak
set RESULT=%ERRORLEVEL%
REM --- 2nd batch ends ---

But it seems the value set to RESULT by 2nd.cmd was discarded when the execution returns to the main batch.
(When 2nd.cmd is called without piping, the value set in 2nd.cmd is effective and the checks work. But this is not what I want.)

Is there no other choice than redirecting the output to a file and then process(display) the file?
I won't be able to see the progress during the long execution time this way. :(

我是ㄞㄟㄣ said...

At the end, my solution was writing the errorlevel to a temp file:

REM --- main batch starts ---
set ResultFile=some_file.txt
call 2nd.cmd 1 | display_and_write_to a1.log
if EXIST %ResultFile% (
set /p RESULT= < %ResultFile%
del /f %ResultFile%
) else (
set RESULT=255
)
if %RESULT% GTR 0 goto :EOF
call 2nd.cmd 2 | display_and_write_to a2.log
if EXIST %ResultFile% (
set /p RESULT= < %ResultFile%
del /f %ResultFile%
) else (
set RESULT=255
)
if %RESULT% GTR 0 goto :EOF
call 2nd.cmd 3 | display_and_write_to a3.log
if EXIST %ResultFile% (
set /p RESULT= < %ResultFile%
del /f %ResultFile%
) else (
set RESULT=255
)
:EOF
REM --- main batch ends ---


REM --- 2nd batch starts ---
make -f %1.mak
echo %ERRORLEVEL% > %ResultFile%
REM --- 2nd batch ends ---


This satisfied my demand. :)

(This blog doesn't accept the less-than / greater-than characters...?)

Arif Sukoco said...

Thanks for sharing the solution, Ian. It's weird that you can't pass the RESULT value back when the call is piped. I tried this too on Vista and confirmed it.