18

In the example provided below, I execute nmake and then redirect STDOUT/STDERR to tee, which then sends it to the screen, and also to a log file. The problem is that I'm trying to capture the exit code for nmake and not tee. What I need is the exit code from nmake, and not tee.

nmake | tee output.txt
dbenham
  • 127,446
  • 28
  • 251
  • 390
doberkofler
  • 9,511
  • 18
  • 74
  • 126

3 Answers3

30

You might think you could do something like the following, but it won't work.

(nmake & call set myError=%%errorlevel%%) | tee output.txt

The problem lies in the mechanism by which Windows pipes work. Each side of the pipe is executed in it's own CMD shell. So any environment variable you set there will disappear once the command has finished. Also the delayed expansion of %errorlevel% is more complicated because of the extra level of parsing, and because the CMD shell has a command line context instead of a batch context.

You could do something like this:

(nmake & call echo %%^^errorlevel%% ^>myError.txt) | tee output.txt
for /f %%A in (myError.txt) do echo nmake returned %%A
del myError.txt

Or you could embed the errorlevel in your output.txt:

(nmake & call echo nmakeReturnCode: %%^^errorlevel%%) | tee output.txt
for /f "tokens=2" %%A in ('findstr /b "nmakeReturnCode:" output.txt') do echo nmake returned %%A

Both solutions above assume you are running the commands in a batch script. If you are executing the commands from the command line instead, then both solutions above need
%^^errorlevel% instead of %%^^errorlevel%%.

But given that nmake does not require user input, and it is usually fast so real time monitoring is probably not an issue, then the simplest solution seems to be

nmake >output.txt
set myError=%errorlevel%
type output.txt
echo nmake returned %myError%


Note - there are many subtle complications when working with Windows pipes. A good reference is Why does delayed expansion fail when inside a piped block of code?. I recommend reading the question and all the answers. The selected answer has the best info, but the other answers help provide context.

EDIT 2015-06-02

I've recently discovered you can use DOSKEY macros to cleanly store and retrieve the ERRORLEVEL from either (or both) sides of a pipe, without resorting to a temporary file. I got the idea from DosTips user Ed Dyreen at http://www.dostips.com/forum/viewtopic.php?p=41409#p41409. DOSKEY macros cannot be executed via batch, but the definitions persist after ENDLOCAL and CMD /C exit!

Here is how you would use it in your situation:

(nmake & call doskey /exename=err err=%%^^errorlevel%%) | tee output.txt
for /f "tokens=2 delims==" %%A in ('doskey /m:err') do echo nmake returned %%A

If you want, you can add one more command at the end to clear the definition of the err "macro" after you have retrieved the value.

doskey /exename=err err=
dbenham
  • 127,446
  • 28
  • 251
  • 390
  • Simple solution defeats major reason for using tee (simultaneous output). Also for the first solution you can use "set /p MY_ERROR= – Sam Mackrill Oct 10 '13 at 11:25
  • What is the deal with `call set`, `call echo` and so forth? I've never seen this syntax before. – alecov Mar 21 '16 at 18:49
  • 2
    @Alek - It is an old hack to get a second round of variable expansion after the initial parse. It is an alternative to using delayed expansion. `echo !var!` is similar to `call echo %%var%%`. Delayed expansion is typically preferred, but there are times when the CALL hack is needed. – dbenham Mar 21 '16 at 19:18
  • 1
    If anyone wants to try the first "Or you could embed the errorlevel in your output.txt" example from the command-line without writing a batch file you'll have to do something like this`(echo Exiting... & cmd /c exit /b 1234 & call echo %^^errorlevel% > myerror.txt) | tee file.txt` since the percentages are only doubled up in batch-files to escape them. – Brent Rittenhouse Jun 07 '19 at 08:05
  • Also, @dbenham, I was confused since you said "DOSKEY macros cannot be executed via batch" but then saw that you were doubling up the percentages in the example which made it look like you could. It was ESPECIALLY strange since .. if you can't use them in batch files the how is this a useable example at all? ... I eventually realized that you meant you CAN use this in batch files because you're only defining it, not executing it, and the definitions last. I don't really know how to word this better but figured it might help to know that without doskey knowledge this is confusing. =/ – Brent Rittenhouse Jun 07 '19 at 08:07
  • Also, I ran out of space to say it but: THANK YOU! You literally cured a headache that was growing out of control with this answer. I really can't express how awesome this was to come across. – Brent Rittenhouse Jun 07 '19 at 08:08
  • Ugh, sorry. One thing to note is that the "simplest" answer is was I was actually trying before coming here. It works just fine UNLESS the command you're executing needs user-input (and you can't redirect the input I guess...). In this situation everything will seem to hang. Took me a second to realize what was going on. People should probably just use the doskey method OR use the output one prefixed with with something like `set _tmp=%temp%\%~n0.%random%` and then outputting via `>> "%_tmp%"`. That way it wouldn't be creating an additional file in the current directory. Just my two cents. =/ – Brent Rittenhouse Jun 07 '19 at 08:13
  • @BrentRittenhouse - Yes, the "simple" answer may or may not work depending on context. For the OPs purpose of nmake, it makes sense. But if you need to provide input, or if you need to monitor the progress as it is executing, then no, it is not an option. I'll update the answer to clarify. – dbenham Jun 07 '19 at 11:55
3

There is a version of "tee" (called mtee => https://ritchielawrence.github.io/mtee/) that optionally adopts the exit code of the piped process. With that you could write

nmake | mtee /E output.txt
Hardy
  • 51
  • 3
  • Doesn't seem to work as advertised for me C:\Temp>c:\Python26\python.exe t.py | h:\apps\mtee /E out3.txt C:\Temp>echo %ERRORLEVEL% 0 C:\Temp>more t.py import sys sys.exit(1) C:\Temp>h:\apps\mtee /? MTEE v2.21 Win32 Commandline Standard Stream Splitter for Windows XP .. 10. Copyright (c) 2001-2016 Ritchie Lawrence – Jamie Feb 05 '21 at 14:44
  • Beware of issues: https://github.com/ritchielawrence/mtee/issues/4 – Andry Mar 09 '22 at 18:19
2

Inspired by the answer of @dbenham, I made the following version:

(%cmd% & call echo EXIT /B %^^ERRORLEVEL% >%ret_file%)
      | tee %log_file% & %ret_file%>NUL

It is a one-liner, that can be used on the command line, or in a batch file. It has the advantage that the line ends up producing the correct %ERRORLEVEL%, so you can evaluate it in the traditional way.

  1. The command %cmd% gets executed, setting %ERRORLEVEL% to the value we need to retrieve.
  2. We'using the CALL mechanism to get manual delayed expansion (so we get the correct value of %ERRORLEVEL%). The call creates a mini-command as a batch file %ret_file%) that exits with the same %ERRORLEVEL% as the original command %cmd%.
  3. The pipe does whatever it needs to do, followed by the execution of the mini-command %ret_file%, setting the %ERRORLEVEL% to the appropriate value.

The disadvantage is that the one-liner litters your directories with little files containing

EXIT /B <some_error_value>

You can easily adjust the one-liner so the script %ret_file% becomes self-erasing upon execution.

Flandraco
  • 991
  • 10
  • 11