What's the Windows batch file equivalent of set -o errexit in a bash script?
I have a long batch file filled with different programs to run on Windows command line... basically its an unrolled make file with every compiler command that needs to be run to build an exe in a long sequence of commands.
The problem with this method is that I want it to exit the batch command on the first non-zero return code generate by a command in the script.
As far as I know, Windows batch files have a problem where they don't automatically exit on the first error without adding a lot of repetitive boilerplate code between each command to check for a non-zero return code and to exit the script.
What I'm wondering about, is there an option similar to bash's set -o errexit for Windows cmd.exe? or perhaps a technique that works to eliminate too much boilerplate error checking code... like you set it up once and then it automatically exits if a command returns a non-zero return code without adding a bunch of junk to your script to do this for you.
(I would accept PowerShell option as well instead of cmd.exe, except PowerShell isn't very nice with old-unix-style command flags like: -dontbreak -y ... breaking those commands without adding junk to your command line like quotes or escape characters... not really something I want to mess around with either...)
CMD/Batch
As Ken mentioned in the comments, CMD does not have an equivalent to the bash option -e (or the equivalent -o errexit). You'd have to check the exit status of each command, which is stored in the variable %errorlevel% (equivalent to $? in bash). Something like
if %errorlevel% neq 0 then exit /b %errorlevel%
PowerShell
PowerShell already automatically terminates script execution on errors in most cases. However, there are two error classes in PowerShell: terminating and non-terminating. The latter just displays an error without terminating script execution. The behavior can be controlled via the variable $ErrorActionPreference:
$ErrorActionPreference = 'Stop': terminate on all errors (terminating and non-terminating)
$ErrorActionPreference = 'Continue' (default): terminate on terminating errors, continue on non-terminating errors
$ErrorActionPreference = 'SilentlyContinue': don't terminate on any error
PowerShell also allows more fine-grained error handling via try/catch statements:
try {
# run command here
} catch [System.SomeException] {
# handle exception of a specific type
} catch [System.OtherException] {
# handle exception of a different type
} catch {
# handle all other exceptions
} finally {
# cleanup statements that are run regardless of whether or not
# an exception was thrown
}
In olden days, I remember a trick we used to use to run Perl scripts within Windows cmd.exe as a simple invocation of a cmd file rather than having to run perl.exe with the script name, something like:
#rem = '
#perl -x -S %0 %*
#goto :eof
#rem ';
<insert Perl script here>
This used the rather neat trick of exploiting differences in how cmd.exe and perl.exe would handle the input script. Windows' cmd.exe would read the first three lines as three separate commands which would:
have a comment with no echo;
run perl with the same input file and arguments, without echo; and
goto the end of the file (exit) with no echo.
On the other hand, perl.exe would treat the first four lines as an assignment statement, then go on to execute the Perl script proper.
Now it's often a pain to run Powershell scripts from the cmd.exe command line since you have to use something like:
powershell -file go.ps1
I'm wondering if there's a way to encode both batch and powershell commands into a single cmd file, similar to the Perl trick, in such a way that it starts running under cmd.exe but switches to Powershell quickly after that.
If that were possible, you could run your script go.cmd simply by entering:
go
at the command line, rather than some convoluted invocation of powershell.
I know that you can ship separate cmd and ps1 files but that's in fact what I'm trying to get away from. I'm looking for a single file solution if possible.
I also know that you can base-64 encode your script or execute it as a string, provided you replace all newlines with semicolons. But that means the Powershell stuff is no longer easily editable or readable in the resulting file.
Fortunately or otherwise, Powershell knows exactly what to do with .cmd files: use CMD to run them.
#set $a=%0 && powershell -encodedcommand ZwBjACAAKAAoAGQAaQByACAAZQBuAHYAOgBgACQAYQApAC4AdgBhAGwAdQBlACAALQByAGUAcABsAGEAYwBlACAAJwAiACcALAAnACcAKQAgAHwAIABzAGUAbABlAGMAdAAgAC0AcwBrAGkAcAAgADIAIAB8ACAAcABvAHcAZQByAHMAaABlAGwAbAAgAC0A
#exit /b
"Hello from Powershell! You know it's me because cmd would never know that 2 + 2 = $(2 + 2)!"
The encoded command is
gc ((dir env:`$a).value -replace '\"','') | select -skip 2 | powershell -
Which fetches the contents of the script invoking itself, skips the stuff intended for CMD and, yes, runs Powershell on the rest.
Since we're piping commands through stdin and not having them in a proper script file there may be weirdness for more complicated scripts, doubly so if you start nesting these hybrids. I wouldn't trust this in production, but hey, it's something.
I am using mr on Windows and it allows running arbitrary commands before/after any repository action. As far as I can see this is done simply by invoking perl's system function. However something seems very wrong with my setup: when making mr run the following batch file, located in d:
#echo off
copy /Y foo.bat bar.bat
I get errors on the most basic windows commands:
d:/foo.bat: line 1: #echo: command not found
d:/foo.bat: line 2: copy: command not found
To make sure mr isn't the problem, I ran perl -e 'system( "d:/foo.bat" )' but the output is the same.
Using xcopy instead of copy, it seems the xcopy command is found since the output is now
d:/foo.bat: line 1: #echo: command not found
Invalid number of parameters
However I have no idea what could be wrong with the parameters. I figured maybe the problem is the batch file hasn't full access to the standard command environment so I tried running it explicitly via perl -e 'system( "cmd /c d:\foo.bat" )' but that just starts cmd and does not run the command (I have to exit the command line to get back to the one where I was).
What is wrong here? A detailed explanation would be great. Also, how do I solve this? I prefer a solution that leaves the batch file as is.
The echo directive is executed directly by the running command-prompt instance.
But perl is launching a new process with your command. You need to run your script within a cmd instance, for those commands to work.
Your cmd /c must work. Check if you have spaces in the path you are supplying to it.
You can use a parametrized way of passing arguments,
#array = qw("/c", "path/to/xyz.bat");
system("cmd.exe", #array);
The echo directive is not an executable and hence, it errors out.
The same is true of the copy command also. It is not an executable, while xcopy.exe is.
I have scrip contain command line:
set dir=%1
cd %dir%
test.bat
echo successful
When run this script, file test.bat (this file run phpunit) run complete then this script don't run command line echo successful.
So, how to try run to eof script.
Use call test.bat.
When you try running a batch file from another batch like in your question control does not pass back to your calling batch.
Side note: I'd usually use pushd/popd for going into directories from batch files. At least I prefer when a batch file doesn't have a side-effect on the shell I'm working on (similar rationale for setlocal). Also this solves the problem when you pass a directory on another drive (although you could do cd /d in that case.
We're starting Matlab from our Jenkins buildserver. As the build may take some time it would be nice to get some log-outputs while matlab is running. Is there a way to print text to standard output? disp, fprintf and java.lang.System.out.printline only write to the matlab console, not to standard output.
Using a logfile or a pipe won't help, as Jenkins only reads from standard-output during a build step.
How can we write log-statements to the standard output while matlab is running?
EDIT:
We're running Matlab 2010b on Windows
Depending what you are doing with Matlab you could probably launch it in command line without GUI. I used this on a server and it behaves pretty much like a shell script and writes to standards outputs.
See the startup options.
I used the following:
/path/to/matlab -nojvm -nodisplay -nosplash -nodesktop -r /path/to/mfile
EDIT: forgot to mention one very important little detail, place an exit command at the end of your mfile or Matlab will hang there waiting.
It seems that the combination of -wait and -log (not -logfile) clones the command window output to the parent console's stdout, but only if you call the MATLAB executable in [MATLABROOT]\bin, not [MATLABROOT]\bin\win64 (the subdirectory for current arch).
Tested on Windows with R2015b and R2016b:
C:\MATLAB\bin\matlab.exe -wait -log
NOT
C:\MATLAB\win64\bin\matlab.exe -wait -log
Remember to put an exit/quit in your script if you are running with -r.
The only trouble is that I can't seem to find any documentation for the -log option! Meh.
There don't seem to be any good ways to do this from within MATLAB. The easiest way I can think of doing this is by using a shell script. You could write a small shell script which would simply print any input to stdout, and then call that shell script from within matlab using the unix (or system) commands. Jenkins should be able to read the command-line output of the script and work with that.
I figured out a way to do this and am also doing it for Jenkins Matlab interface on windows.
Basic idea is that you will use diary command, but then tail -f the file, but you need a smart way to kill the tail command if you open multiple matlab instances because there will be name collisions. So the method I'm using is to name the file log.txt where the PID used is MATLAB's PID it is using when it opens.
There is an undocumented feature in MATLAB that allows you to get its PID. So now, both your batch file and MATLAB know the PID without having to read/write to a random text file that will get messy when executing multiple jobs. So the PID you use that as your unique identifier. The PID of "tail -f" is also used by MATLAB to kill tail -f to make the batch file die and is found by MATLAB using the commandline details associated with the process invocation since it uses again the unique PID log file name.
This uses some wmic commands and needs Windows Vista/7 or above. With XP you probably have to work harder to get the process ID's but should be still possible.
Here is what to do:
1) Get gnu awk for windows: http://gnuwin32.sourceforge.net/packages/gawk.htm
2) Get tail.exe from windows resource kit: http://www.microsoft.com/en-us/download/details.aspx?id=17657
3) Make sure tail and awk are in your path (the windows resourece kit I don't think automatically puts them in the path)
3) Create a batch file called matlabrun.bat as follows, (note: you need the #echo off, also the entire command is quite long, scroll right..)
#echo off
wmic process call create "c:\matlab\bin\win64\matlab.exe -r \"cd('c:\jenkins\workspace\test'); workdir=pwd; outpath=[pwd '\output'] ; try; run('C:\MATLAB\work\test_run'); end; quit; \" " | findstr ProcessId | awk "{print $3}" | awk -F";" "{ print $1 }"
4) Create another batch file called run.bat with:
for /f %%i in ('matlabrun.bat') do (
echo MATLAB Log... > log%%i.txt
tail -f log%%i.txt
set logfilename=log%%i.txt
goto next
)
:next
del /f %logfilename%
5) The run.bat file will execute matlabrun.bat and since -wait is not passed, matlab will immediately return to the command line and execute the tail -f command. That will block the batch file from completing until you kill it. matlabrun.bat returns the PID of matlab.
6) Another important note: since you are using "wmic process create" which will provide you with a PID that MATLAB is using, but will default to a working directory of c:\windows\system32. So that is why I pass the work directory to matlab. wmic process create is also a bit particular about what parameters you put into your command string for matlab to run. So it appears to have a problem with using commas in your command string. So I suggest not to use those, or figure out how to escape them (it might be that ^, works, but I just removed my commas anyway in my matlab run command).
6) The "test_run.m" file contains the following code to write to the correct log file and to kill correct tail -f instance.
matlabpid=feature('getpid');
filename=['log',num2str(matlabpid),'.txt'];
filenamefull=[workdir,'\',filename];
diary(filenamefull);
disp('Script starting...')
%%% put your code here %%%
disp('Script completed...');
diary off;
%%% FIND PID of tail.exe and kill it
%%% by using the name of the log file in the process command line
[a,b]=dos(['wmic process get Commandline,ProcessId']);
C=textscan(b,'%s','delimiter','\n');C=C{1};
for jj=1:size(C,1),
if strfind(C{jj},filename),
D=textscan(C{jj},'%s');D=D{1};
dos(['taskkill /f /pid ',D{4}]) %kills tail.exe which is the log watcher
break
end
end
7) You start it by doing run.bat. It will go and execute matlab, then start tailing the output while MATLAB runs in real-time. Then when done it will delete the log file.
8) My directory structure / files are in these locations (I'm using win7 64bit):
c:\jenkins\workspace\test\tail.exe
c:\jenkins\workspace\test\awk.exe
c:\jenkins\workspace\test\matlabrun.bat
c:\jenkins\workspace\test\run.bat
c:\matlab\work\test_run.m
c:\matlab\bin\win64\matlab.exe
If you are using 32bit matlab, point it to the win32 directory. To get the correct PID, you need to specify the actualy matlab.exe binary in the win32 or win64 directory.
You can do this by pointing the -logfile option to the Jenkins log file. Something like the following:
"C:\path\to\matlab.exe" "-r" "functionToRun" "-logfile" "%JENKINS_HOME%\jobs\%JOB_NAME%\builds\%BUILD_NUMBER%\log" /wait
You can use the diary mode. Not sure if it will fit your specific implementation.
http://www.mathworks.com/help/techdoc/ref/diary.html
I didn't find a real solution. Mathworks created some wrapper tool. But this will only output the results after matlab has exited. You won't get any ouput during execution.
http://www.mathworks.de/support/solutions/en/data/1-ACT3YN/index.html?product=ML&solution=1-ACT3YN
So I'll have live without real live-output...
Or try using '-logfile' option in matlab.
matlab.exe -nodisplay -nosplash -nodesktop -wait -logfile logfile.txt -r "try script.m ;catch err; disp(err.message); end ; exit"
I prefer using bash (Execute shell) in Jenkins, then you can tail the log-file while matlab is running.
matlab.exe <...> &
matpid=$!
tail -f logfile.txt &
tailpid=$!
wait $matpid
matexit=$?
kill $tailpid
sleep 1 # Just to make sure kill is done before Jenkins step ends and no zombie processes
exit $matexit