I have a batch file that executes a java application. I'm trying to modify it so that whenever an exception occurs, it'll write the STDERR out to a file.
It looks something like this:
start java something.jar method %1 %2 2>> log.txt
Is there a way I can write the arguments %1 and %2 to the log.txt file as well? I don't want to write it to the log file everytime this batch file gets called, only when an exception occurs.
I tried searching for a way to redirect STDERR into a variable, but I couldn't figure it out. Ideally I'd like the log file to look something like:
Batch file called with parameters:
- "first arg"
- "second arg"
Exception:
java.io.exception etc...
------------------------------------
Batch file called with parameters:
- "first arg"
- "second arg"
Exception:
java.io.exception etc...
Something like this might work:
javastart.cmd
#echo off
set params=%*
for /f "delims=" %%e in ('java something.jar method %1 %2 ^>nul') do (
echo Batch file called with parameters:>>log.txt
echo - Args[]: %*>>log.txt
echo - Arg[1]: %1>>log.txt
echo - Arg[2]: %2>>log.txt
echo Exception: %%e
)
I am not a java programmer myself, I cannot test this output/situation but:
1: %* means every parameters, from 1st to last (even %12 although it's not directly available.. ) whatever the formating. Might be better to use this than delimit those. In the even of bad spacing/quoting, you would have the full parameters too. I added a line in the log, to show the full line then the parameters. You can see where the problem was if it's a matter of bad arguments.
2: sending the stdout to null (^>nul) will only keep the stderr output, set into %%e
3: Whatever is in the do part will only happen is the for test statement actually has anything as an output, i.e. if %%e is set.
With those being said, you have to test the script and see if the exception is actually sent to stderr or, like some other softwares, to stdout even if an error occured.
I hope this helps.
The only working solution I see would be to redirect stderr to a temporary file
java blah.jar %1 %2 2>stderr
and afterwards looking whether something has been written to the file and writing to the log in that case.
for %%i in (stderr) do if %%~zi GTR 0 (
echo Parameters: %1 %2 >>log.txt
type stderr >> log.txt
)
If the batches aren't run in sequence but rather simultaneously you need to find something to uniquify the temp variable:
set file=%time::=%
set /a file=file
set file=%file%%random%
java blah.jar %1 %2 2>stderr_%file%
for %%i in (stderr) do if %%~zi GTR 0 (
echo Parameters: %1 %2 >>log.txt
type stderr >> log.txt
)
This will avoid clashes between multiple running batches. However, you are currently not using anything to lock writing to your log file and things may appear out of place when other things get written to it (with other batches you might get interleaving in the echo and type commands or, when you're redirecting output of java to that file as well, then it may get mixed up with regular output of the java program:
Parameters: foo bar
Some regular output
Parameters: foo2 bar2
More output
NullPointerException at Blah: What we really wanted to have right after the parameters
IOException at Blah: This exception belongs to the parameters foo2 bar2
You can use another file as semaphore for writing to the log to avoid the batch outputs getting mixed: create the file [copy nul file] when you want to write to the log, delete it afterwards and before you attempt to create it check whether it is actually there and wait until it disappears. You can't do anything about the stdout log being mixed into the file, though, except you use that temp file approach for stdout as well (and simply type the stdout log to log.txt when the Java program finished, but, again with using the semaphore.
I don't get exactly what you are asking, but here's some info that may help...
You can redirect stderr separately from stdout in a .cmd or .bat file. To redirect stderr, use something like this:
MyCommand.exe > command.stdout.txt 2> command.stderr.txt
Then, you can check the command.stderr.txt for content, and if any is present, concatenate it to the command.stdout.txt into your log file. Or you could concat it in any case. If you like you could also echo the command that you ran, into the final log file.
You can also check for the exit code in a batch file, using the %ERRORLEVEL% env var. Exe files are expected to set ERRORLEVEL in case of an error. I don't know if java.exe does this. It should, if it is a good citizen on Windows. This might be an alternative way of finding out if the Java app exited with an error condition. But this is no guarantee that stderr got nothing. For example, a Java app might print out an exception and stack trace, and then exit with code 0, which indicates success. In which case stderr would have gunk in it, but ERRORLEVEL would be zero.
EDIT: s/ERROR_LEVEL/ERRORLEVEL
How about something like this (untested):
#echo off
#echo Batch file called with parameters: >> log.txt
#echo - %1 >> log.txt
#echo - %2 >> log.txt
start java something.jar method %1 %2 2>> log.txt
Try using ERRORLEVEL
java something.jar method %1 %2 2>> log.txt
IF %ERRORLEVEL% NEQ 0 GOTO Error
Error:
#ECHO There was an error
#ECHO Arg 1: %1 >> log.txt
#ECHO Arg 2: %2 >> log.txt
A batch file like this should do the trick.
start_prog.cmd
CALL :Start_Prog arg1 arg2
GOTO :EOF
:Start_Prog
something.py %1 %2 2>&1 1>nul | "C:\Python26\python.exe" format_output.py %1 %2 >>log.txt
GOTO :EOF
Here I'm passing stderr via a pipe and passing arguments as arguments. The output is then appended to a log.txt file.
Throw away stdout
1>nul
Redirect stderr to stdout
2>&1
Pipe the stderr into a script to format the output, and pass arguments as arguments.
| "C:\Python26\python.exe" format_output.py %1 %2
Append output to a "log.txt".
>>log.txt
On my system I need to call python.exe otherwise the pipe doesn't work.
Here are the two files I used "something.py" and "format_output.py".
something.py
import sys
print >>sys.stdout, " ".join(sys.argv[1:])
print >>sys.stderr, "java.io.exception etc..."
format_output.py
import sys
print "Batch file called with parameters:"
for arg in sys.argv[1:]:
print '- "{0}"'.format(arg)
print "Exception:"
for line in sys.stdin:
print line
And finaly here's the output.
log.txt
Batch file called with parameters:
- "arg1"
- "arg2"
Exception:
java.io.exception etc...
The only thing missing is that something is always written to the "log.txt" file.
To tidy this up further I would move writing the log file into "format_output.py".
You could then add a check to see if the stderr from your program is blank.
Related
I am trying to make a Windows batch file (the main script) redirect all of it's output to a file while still displaying it on the screen. I cannot change all the code that calls the main script - I need to change the main script itself.
I have a solution that requires "bootstrapping" the main script by calling it again from another bat file (tee_start.bat).
What I'm sure I want to avoid is refactoring all the other scripts that call main.bat to use the tee command - it will be a much smaller, safer change if I can put some new code in main.bat.
Is there a way to do this that does not involve restarting the main file as I am doing below?
For example, is there a cmd.exe or powershell.exe command that says "Take my current STDOUT and tee it" - or does tee somehow support this behavior and I missed it?
Or, alternatively, should I settle for what I have implemented as the least invasive method?
I have implemented a solution as follows:
main.bat
REM this is an example of bootstrap mode - it will restart this main script with all output logged
call tee_start.bat %~dpnx0 %*
echo This output should appear both in the screen console and in a log file
tee_start.bat
REM in order to bootstrap the output redirection - this script gets called twice - once to initialize logging with powershell tee command
REM second time, it just echoes log information and returns without doing anything
if x%LOG_FILE% neq x (
echo Bootstrapped Process: %1%
echo Escaped Arguments: %ADDL_ARGS%
echo Log File: %LOG_FILE%
goto :EOF
)
set ADDL_ARGS=
:loop_start
shift
if x%1 == x goto :after_loop
REM preserve argument structure (quoted or not) from the input
set ADDL_ARGS=%ADDL_ARGS% \"%~1\"
goto loop_start
:after_loop
SET LOG_FILE=/path/to/some/logfile.log
powershell "%1% %ADDL_ARGS% 2>&1 | tee -Append %LOG_FILE%"
I suggest the following approach, which makes do without the aux. tee_start.bat file:
main.bat content (outputs to log.txt in the current folder; adjust as needed):
#echo off & setlocal
if defined LOGFILE goto :DO_IT
set "LOGFILE=log.txt"
:: Reinvoke with teeing
"%~f0" %* 2>&1 | powershell -NoProfile -Command "$input | Tee-Object -FilePath \"%LOGFILE%\"; \"[output captured in: %LOGFILE%]\""
exit /b
:DO_IT
:: Sample output
echo [args: %*]
echo to stdout
echo to stderr >&2
echo [done]
Tee-Object uses a fixed character encoding, which is notably "Unicode" (UTF-16LE) in Windows PowerShell (powershell.exe), and (BOM-less UTF-8) in PowerShell (Core) 7+ (pwsh.exe).
I have a simple health check script for checking status of windows share drives listed below.
I am getting the desired output when the command works. However, when there is an error, I do not get output in the output file. I have looked at STDOUT and STDERR as a possible solution, but I am not able get this to work yet.
Can anyone please help me with this code.
#echo off
echo START > results.txt
for /f %%s in (server_list.txt) do (
echo %%s >> results.txt
for /f "delims=" %%n in ('net use \\%%s') do (
echo %%s - %%n >> results.txt 2> err.txt
)
)
The output file looks like this.
START
server1
server1 - The command completed successfully.
server2
server2 - The command completed successfully.
However, there is nothing in the err.txt file. I have made sure to put some incorrect entries in file server_list.txt to get a few errors for testing. The error output is displayed in the command line window instead of being printed to the err.txt file.
First, read the Microsoft article about Using Command Redirection Operators.
Second, the command FOR executes the command line net use \\%%s in a new command process using cmd.exe in background which means with no visible console window. The output of this background command process written to STDOUT is captured by FOR and processed next line by line. The error output written to STDERR is redirected by FOR to error output of currently running process which is the reason why the error output can be seen in opened console window of command process executing the batch file.
The command line echo %%s - %%n >> results.txt 2> err.txt outputs the output of background command process executing net use captured by FOR and assigned to loop variable n to STDOUT of current command process which is redirected to file results.txt.
Additionally the error output by command ECHO is redirected to file err.txt if ECHO would output an error message. But ECHO does never output an error message. That is the reason why err.txt does not contain any error message output by command NET.
I suggest to capture with FOR standard and error output of net use and evaluate the output in body of FOR loop.
#echo off
setlocal EnableExtensions EnableDelayedExpansion
del err.txt 2>nul
echo START > results.txt
for /F %%I in (server_list.txt) do (
for /f "delims=" %%J in ('%SystemRoot%\System32\net.exe use "\\%%I" 2^>^&1') do (
set "NetOutput=%%J"
if not "!NetOutput:successfully=!" == "!NetOutput!" (
echo %%I - %%J>>results.txt
) else (
echo %%I - %%J>>err.txt
)
)
)
endlocal
When the line output by command NET from any output contains the word successfully the line from server_list.txt and the success message is written to file results.txt. Otherwise the most likely error output is written also with line from server_list.txt to file err.txt.
If we have a batch file being redirected to a log like so:
C:\Testing\Example.bat > C:\Testing\Example.log
Is there any way inside the batch file to determine if there's a standard output redirect happening to a log file?
Basically the batch file I have requires three arguments passed to it. When arguments are left out, the batch file prints a usage example much like regular Windows commands would, and throws a 'pause' out so instructions can be read. However if the batch is called without arguments AND it's being logged, then the batch file will just sit there forever waiting for a key to break the pause, but won't show anything on the screen.
Normally this wouldn't be a problem at all, however I'm trying to make this idiot-proof since I won't be the one implementing the batch file in other scripts/scheduled tasks.
At this point it seems like I need to get rid of the usage pause entirely, but I was hoping for a solution where I wouldn't have to. Thanks for the help.
There is no good way to determine if stdin or stdout has been redirected using native batch commands.
But there is no need to worry about redirection in your case. Simply redirect the PAUSE stdin and stdout to con, and you don't have to worry about prior redirection.
pause <con >con
Update
It is possible to cleanly detect whether stdin and/or stdout and/or stderr has likely been redirected. But I haven't figured out a way to non-destructively determine which of those handles was redirected.
#echo off
2>nul (5>&4 break) && (
>con echo There has been redirection
) || (
>con echo No redirection found
)
The technique relies on the fact that whenever an existing handle is redirected, the old definition is saved in the lowest available undefined handle.
If there has not been any redirection, then 2>nul saves stderr in 3, and 4 is undefined. So 5>&4 fails, and the || branch is fired.
If there has been redirection, then 3 has already been defined to preserve the original value of the redirected handle, so 2>nul will save stderr in 4 (unless 4 has also already been used by some other redirection). Either way, the 5>&4 will succeed because 4 is defined, so the && branch will fire.
If the test reports that there has been redirection, then at least one of the following must be true:
stdin redirection
stdout redirection
stderr redirection
handle 4 defined directly with something like 4>"log.txt"
handle 5 defined directly with something like 5>"log.txt"
Unfortunately, I cannot determine which of the above are true, except for...
Over at DosTips, SiberiaMan has published a simple technique to determine if stdin has been redirected or is receiving a pipe:
2>nul >nul timeout /t 0 && (
>con echo stdin not redirected or piped
) || (
>con echo stdin has been redirected or is receiving piped input
)
I am not aware of any way to do this using only batch and standard commands. However, there are tricks that can be used from other languages such as native C or C# programs, if it's important enough to you to include another .exe along with your .bat.
Here is a sneaky approach. This batch file creates a .com file, then calls it to test the stdout handle: http://www.dostips.com/forum/viewtopic.php?f=3&t=2800
This explains how to do it with C#, if you want to write a simple C# helper programme: Check if output is redirected. That makes me think it should also be possible from Powershell, but I haven't tried.
There isn't a way in standard Windows batch files (that I'm aware of) to determine where STDOUT is being redirected, or even if it is being redirected. However, you might wish to write your usage examples out to STDERR instead of STDOUT, so at least a simple redirect into a file will not capture the usage info.
Example test.cmd:
#ECHO OFF
ECHO Test output 1>&2
PAUSE 1>&2
Then call it:
test.cmd > output.log
Which will still output:
Test output
Press any key to continue . . .
Of course, this does nothing for when the file is called with STDERR being redirected.
test.cmd 2> error.log
I think you just ask the wrong question. you want to find a way to end the batch with extra break when in cmd mode. and stop in batch mode without pausing.
You can use CMDpause
this will make a variable named cmdpause
examples
https://www.administrator.de/wissen/batchcode-erstellen-fehler-batch-leichter-finden-184736.html
https://www.dostips.com/forum/viewtopic.php?t=7257#p47510
there is something from Dave and jeb. But I have no link.
Phil
You could check if stdout is redirected with a small trick.
It uses the fact that outputting backspaces after a TAB moves the cursor back and if the cursor shall be moved before the home position in this situation, an error will be created to get a hint in your logfiles for the strange characters.
The drawback is, that it outputs <FF><TAB>><BACKSPACE><BACKSPACE><CR><LF> to stdout.
When stdout is the console, it simply clears it.
But when stdout is redirected to a file you append these characters to the file, but it can be commented with a meaningful text.
#echo off
setlocal
call :createTAB_BS
echo Script started, test for redirection:
cls
( echo %%TAB%%%%BS%%%%BS%%) | ( findstr "^" 2^> nul)
if %errorlevel% EQU 0 (
>CON echo stdout is redirected
) ELSE (
echo stdout goes to the console
)
exit /b
:createTAB_BS
for /f "tokens=1,2 delims=," %%A in ('forfiles /p "%~dp0." /m "%~nx0" /c "cmd /c echo(0x09,0x08"') do (
set "TAB=%%A"
set "BS=%%B"
)
exit /b
On Windows, I have written a simple bat script that calls another tool. However, this tool outputs a few specific debug lines when using certain options (seems to be a bug in the original code, which I can not/don't want to modify).
Is there a way to suppress lines output by a command that matches a certain pattern (like starting with DEBUG: )?
(FYIO: the tool is latexdiff.exe, and there are some print STDERR "DEBUG:... lines in the perl source code that are not conditional to the debug variable and printed everytime the --flatten option is used. I don't want to suppress stderr completely either.)
You could try the following:
latexdiff.exe 2>&1| findstr /v /b "DEBUG:"
The /v option basically turns the pattern around, i.e. findstr.exe will let everything through, not matching the pattern. The /b option simply says that the pattern should occur at the beginning of a line.
The 2>&1 redirects STDERR to STDOUT and is required because, as you said, the lines are written to STDERR, not STDOUT. Note that as a "side effect" all output now is written to STDOUT.
Update if there is other output on STDOUT that you need to have, you could do something like this
latexdiff.exe old.tex new.tex > diff.tex 2> latexdiff.stderr
type latexdiff.stderr | findstr /v /b "DEBUG:"
That is, redirect STDOUT to your diff file, redirect STDERR to some file. Afterwards, you just type the file to see error messages.
You might want to put that into a batch file of it's own, like so:
#echo off
setlocal
REM determine a suitable temporary filename
set error_file=%TEMP%\latexdiff.%RANDOM%.stderr
REM run actual diff and save its exit code for later
latexdiff.exe "%~1" "%~2" > "%~3" 2> "%error_file%"
set error_level=%ERRORLEVEL%
REM dump error messages
type "%error_file%" | findstr /v /b "DEBUG:"
REM remove temporary error file and exit with latexdiff's exit code.
del /q "%error_file%"
exit /b %error_level%
You can then call it like: latexdiff_wrapper.cmd old.tex new.tex diff.tex. Appart from using temporary files, the downside is, that error messages will not appear while processing, but at the very end. If that is not an issue, it shouldn't because the diff should be fast, you might find that solution useful.
I have a batch script that executes a task and sends the output to a text file. Is there a way to have the output show on the console window as well?
For Example:
c:\Windows>dir > windows-dir.txt
Is there a way to have the output of dir display in the console window as well as put it into the text file?
No, you can't with pure redirection.
But with some tricks (like tee.bat) you can.
I try to explain the redirection a bit.
You redirect one of the ten streams with > file or < file
It is unimportant, if the redirection is before or after the command,
so these two lines are nearly the same.
dir > file.txt
> file.txt dir
The redirection in this example is only a shortcut for 1>, this means the stream 1 (STDOUT) will be redirected.
So you can redirect any stream with prepending the number like 2> err.txt and it is also allowed to redirect multiple streams in one line.
dir 1> files.txt 2> err.txt 3> nothing.txt
In this example the "standard output" will go into files.txt, all errors will be in err.txt and the stream3 will go into nothing.txt (DIR doesn't use the stream 3).
Stream0 is STDIN
Stream1 is STDOUT
Stream2 is STDERR
Stream3-9 are not used
But what happens if you try to redirect the same stream multiple times?
dir > files.txt > two.txt
"There can be only one", and it is always the last one!
So it is equal to dir > two.txt
Ok, there is one extra possibility, redirecting a stream to another stream.
dir 1>files.txt 2>&1
2>&1 redirects stream2 to stream1 and 1>files.txt redirects all to files.txt.
The order is important here!
dir ... 1>nul 2>&1
dir ... 2>&1 1>nul
are different. The first one redirects all (STDOUT and STDERR) to NUL,
but the second line redirects the STDOUT to NUL and STDERR to the "empty" STDOUT.
As one conclusion, it is obvious why the examples of Otávio Décio and andynormancx can't work.
command > file >&1
dir > file.txt >&2
Both try to redirect stream1 two times, but "There can be only one", and it's always the last one.
So you get
command 1>&1
dir 1>&2
And in the first sample redirecting of stream1 to stream1 is not allowed (and not very useful).
Just use the Windows version of the UNIX tee command (found from http://unxutils.sourceforge.net) in this way:
mycommand > tee outpu_file.txt
If you also need the STDERR output, then use the following.
The 2>&1 combines the STDERR output into STDOUT (the primary stream).
mycommand 2>&1 | tee output_file.txt
If you don't need the output in real time (i.e. as the program is writing it) you could add
type windows-dir.txt
after that line.
The solution that worked for me was: dir > a.txt | type a.txt.
Yes, there is a way to show a single command output on the console (screen) and in a file. Using your example, use...
#ECHO OFF
FOR /F "tokens=*" %%I IN ('DIR') DO ECHO %%I & ECHO %%I>>windows-dir.txt
Detailed explanation:
The FOR command parses the output of a command or text into a variable, which can be referenced multiple times.
For a command, such as DIR /B, enclose in single quotes as shown in example below. Replace the DIR /B text with your desired command.
FOR /F "tokens=*" %%I IN ('DIR /B') DO ECHO %%I & ECHO %%I>>FILE.TXT
For displaying text, enclose text in double quotes as shown in example below.
FOR /F "tokens=*" %%I IN ("Find this text on console (screen) and in file") DO ECHO %%I & ECHO %%I>>FILE.TXT
... And with line wrapping...
FOR /F "tokens=*" %%I IN ("Find this text on console (screen) and in file") DO (
ECHO %%I & ECHO %%I>>FILE.TXT
)
If you have times when you want the output only on console (screen), and other times sent only to file, and other times sent to both, specify the "DO" clause of the FOR loop using a variable, as shown below with %TOECHOWHERE%.
#ECHO OFF
FOR %%I IN (TRUE FALSE) DO (
FOR %%J IN (TRUE FALSE) DO (
SET TOSCREEN=%%I & SET TOFILE=%%J & CALL :Runit)
)
GOTO :Finish
:Runit
REM Both TOSCREEN and TOFILE get assigned a trailing space in the FOR loops
REM above when the FOR loops are evaluating the first item in the list,
REM "TRUE". So, the first value of TOSCREEN is "TRUE " (with a trailing
REM space), the second value is "FALSE" (no trailing or leading space).
REM Adding the ": =" text after "TOSCREEN" tells the command processor to
REM remove all spaces from the value in the "TOSCREEN" variable.
IF "%TOSCREEN: =%"=="TRUE" (
IF "%TOFILE: =%"=="TRUE" (
SET TEXT=On screen, and in "FILE.TXT"
SET TOECHOWHERE="ECHO %%I & ECHO %%I>>FILE.TXT"
) ELSE (
SET TEXT=On screen, not in "FILE.TXT"
SET TOECHOWHERE="ECHO %%I"
)
) ELSE (
IF "%TOFILE: =%"=="TRUE" (
SET TEXT=Not on screen, but in "FILE.TXT"
SET TOECHOWHERE="ECHO %%I>>FILE.txt"
) ELSE (
SET TEXT=Not on screen, nor in "FILE.TXT"
SET TOECHOWHERE="ECHO %%I>NUL"
)
)
FOR /F "tokens=*" %%I IN ("%TEXT%") DO %TOECHOWHERE:~1,-1%
GOTO :eof
:Finish
ECHO Finished [this text to console (screen) only].
PAUSE
My option was this:
Create a subroutine that takes in the message and automates the process of sending it to both console and log file.
setlocal
set logfile=logfile.log
call :screenandlog "%DATE% %TIME% This message goes to the screen and to the log"
goto :eof
:screenandlog
set message=%~1
echo %message% & echo %message% >> %logfile%
exit /b
If you add a variable to the message, be sure to remove the quotes in it before sending it to the subroutine or it can screw your batch.
Of course this only works for echoing.
command > file >&1
If you want to append instead of replace the output file, you may want to use
dir 1>> files.txt 2>> err.txt
or
dir 1>> files.txt 2>>&1
I made a simple C# console which can handle real-time output to both cmd screen and log
class Tee
{
static int Main(string[] args)
{
try
{
string logFilePath = Path.GetFullPath(args[0]);
using (StreamWriter writer = new StreamWriter(logFilePath, true))
{
for (int value; (value = Console.In.Read()) != -1;)
{
var word = Char.ConvertFromUtf32(value);
Console.Write(word);
writer.Write(word);
}
}
}
catch (Exception)
{
return 1;
}
return 0;
}
}
The batch file usage is the same as how you use Unix tee
foo | tee xxx.log
And here is the repository which includes the Tee.exe in case you don't have tool to compile
https://github.com/iamshiao/Tee
I like atn's answer, but it was not as trivial for me to download as wintee, which is also open source and only gives the tee functionality (useful if you just want tee and not the entire set of unix utilities). I learned about this from davor's answer to Displaying Windows command prompt output and redirecting it to a file, where you also find reference to the unix utilities.
The solution provided by "Tomas R" works perfect for the OP's question and it natively available.
Try:
chkdsk c: > output.txt | type output.txt
The output is of this command involves a completion percentage that gets serially output to the file hence it will look a bit messy (i.e. the text will be appended as it progress). This does not happen to the bits that gets output to STDOUT (the screen). It is how it would be if you just do the same command without redirection.
I think you want something along the lines of this:
echo Your Msg> YourTxtFile.txt
Or if you want a new line:
echo Your Msg>> YourTxtFile.txt
These commands are great for logs.
Note: This will sometimes glitch and replace the whole text file on my computer.
Another Note: If the file does not exist, it will create the file.