I am running a program and want to see what its return code is (since it returns different codes based on different errors).
I know in Bash I can do this by running
echo $?
What do I do when using cmd.exe on Windows?
The "exit code" is stored in a shell variable named errorlevel.
The errorlevel is set at the end of a console application. Windows applications behave a little differently; see #gary's answer below.
Use the if command keyword errorlevel for comparison:
if errorlevel <n> (<statements>)
Which will execute statements when the errorlevel is greater than or equal to n. Execute if /? for details.
A shell variable named errorlevel contains the value as a string and can be dereferenced by wrapping with %'s.
Example script:
my_nifty_exe.exe
rem Give resolution instructions for known exit codes.
rem Ignore exit code 1.
rem Otherwise give a generic error message.
if %errorlevel%==7 (
echo "Replace magnetic tape."
) else if %errorlevel%==3 (
echo "Extinguish the printer."
) else if errorlevel 2 (
echo Unknown Error: %errorlevel% refer to Run Book documentation.
) else (
echo "Success!"
)
Warning: An environment variable named errorlevel, if it exists, will override the shell variable named errorlevel. if errorlevel tests are not affected.
Testing ErrorLevel works for console applications, but as hinted at by dmihailescu, this won't work if you're trying to run a windowed application (e.g. Win32-based) from a command prompt. A windowed application will run in the background, and control will return immediately to the command prompt (most likely with an ErrorLevel of zero to indicate that the process was created successfully). When a windowed application eventually exits, its exit status is lost.
Instead of using the console-based C++ launcher mentioned elsewhere, though, a simpler alternative is to start a windowed application using the command prompt's START /WAIT command. This will start the windowed application, wait for it to exit, and then return control to the command prompt with the exit status of the process set in ErrorLevel.
start /wait something.exe
echo %errorlevel%
Use the built-in ERRORLEVEL Variable:
echo %ERRORLEVEL%
But beware if an application has defined an environment variable named ERRORLEVEL!
If you want to match the error code exactly (eg equals 0), use this:
#echo off
my_nify_exe.exe
if %ERRORLEVEL% EQU 0 (
echo Success
) else (
echo Failure Reason Given is %errorlevel%
exit /b %errorlevel%
)
Note that if errorlevel 0 matches errorlevel >= 0.
See if /?.
Or, if you don't handle success:
if %ERRORLEVEL% NEQ 0 (
echo Failed with exit-code: %errorlevel%
exit /b %errorlevel%
)
It's worth noting that .BAT and .CMD files operate differently.
Reading https://ss64.com/nt/errorlevel.html it notes the following:
There is a key difference between the way .CMD and .BAT batch files set errorlevels:
An old .BAT batch script running the 'new' internal commands: APPEND, ASSOC, PATH, PROMPT, FTYPE and SET will only set ERRORLEVEL if an error occurs. So if you have two commands in the batch script and the first fails, the ERRORLEVEL will remain set even after the second command succeeds.
This can make debugging a problem BAT script more difficult, a CMD batch script is more consistent and will set ERRORLEVEL after every command that you run [source].
This was causing me no end of grief as I was executing successive commands, but the ERRORLEVEL would remain unchanged even in the event of a failure.
It might not work correctly when using a program that is not attached to the console, because that app might still be running while you think you have the exit code.
A solution to do it in C++ looks like below:
#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
#include "tchar.h"
#include "stdio.h"
#include "shellapi.h"
int _tmain( int argc, TCHAR *argv[] )
{
CString cmdline(GetCommandLineW());
cmdline.TrimLeft('\"');
CString self(argv[0]);
self.Trim('\"');
CString args = cmdline.Mid(self.GetLength()+1);
args.TrimLeft(_T("\" "));
printf("Arguments passed: '%ws'\n",args);
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory( &si, sizeof(si) );
si.cb = sizeof(si);
ZeroMemory( &pi, sizeof(pi) );
if( argc < 2 )
{
printf("Usage: %s arg1,arg2....\n", argv[0]);
return -1;
}
CString strCmd(args);
// Start the child process.
if( !CreateProcess( NULL, // No module name (use command line)
(LPTSTR)(strCmd.GetString()), // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
0, // No creation flags
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi ) // Pointer to PROCESS_INFORMATION structure
)
{
printf( "CreateProcess failed (%d)\n", GetLastError() );
return GetLastError();
}
else
printf( "Waiting for \"%ws\" to exit.....\n", strCmd );
// Wait until child process exits.
WaitForSingleObject( pi.hProcess, INFINITE );
int result = -1;
if(!GetExitCodeProcess(pi.hProcess,(LPDWORD)&result))
{
printf("GetExitCodeProcess() failed (%d)\n", GetLastError() );
}
else
printf("The exit code for '%ws' is %d\n",(LPTSTR)(strCmd.GetString()), result );
// Close process and thread handles.
CloseHandle( pi.hProcess );
CloseHandle( pi.hThread );
return result;
}
At one point I needed to accurately push log events from Cygwin to the Windows Event log. I wanted the messages in WEVL to be custom, have the correct exit code, details, priorities, message, etc. So I created a little Bash script to take care of this. Here it is on GitHub, logit.sh.
Some excerpts:
usage: logit.sh [-h] [-p] [-i=n] [-s] <description>
example: logit.sh -p error -i 501 -s myscript.sh "failed to run the mount command"
Here is the temporary file contents part:
LGT_TEMP_FILE="$(mktemp --suffix .cmd)"
cat<<EOF>$LGT_TEMP_FILE
#echo off
set LGT_EXITCODE="$LGT_ID"
exit /b %LGT_ID%
EOF
unix2dos "$LGT_TEMP_FILE"
Here is a function to to create events in WEVL:
__create_event () {
local cmd="eventcreate /ID $LGT_ID /L Application /SO $LGT_SOURCE /T $LGT_PRIORITY /D "
if [[ "$1" == *';'* ]]; then
local IFS=';'
for i in "$1"; do
$cmd "$i" &>/dev/null
done
else
$cmd "$LGT_DESC" &>/dev/null
fi
}
Executing the batch script and calling on __create_event:
cmd /c "$(cygpath -wa "$LGT_TEMP_FILE")"
__create_event
Related
Here is my test.cpp program. It exits abnormally via an assert(0).
#include <cassert>
int main() {
assert(0);
}
When I run this program directly I get the expected output including a non-zero exit status:
$ ./test
...
$ echo $?
134
But when I try to detect the abnormal exit in tcl/expect I don't seem to be able to:
#!/usr/bin/expect
spawn ./test
expect eof
lassign [wait] pid spawnid os_error_flag value
if {$os_error_flag != 0} {
puts "OS error"
exit 1
}
if {$value != 0} {
puts "Application error"
exit 1
}
puts "No error"
When I run that script:
$ ./test.expect
No error
If I use exit(1) instead of assert(0) then the tcl script is able to detect the abnormal exit. Why doesn't tcl/expect provide an OS- or application-returned error code for assertion failures and how can I uniformly detect all abnormal program exits by checking the exit code?
Not an answer, but an extended comment:
Running that code, I see:
$ ./a.out; echo $?
Assertion failed: (0), function main, file x.c, line 4.
Abort trap: 6
134
And in expect, I see:
$ expect
expect1.1> spawn ./a.out
spawn ./a.out
47429
expect1.2> expect eof
Assertion failed: (0), function main, file x.c, line 4.
expect1.3> wait
47429 exp6 0 0 CHILDKILLED SIGABRT SIGABRT
Looks like you need to look at the elements returned by wait beyond the 4th.
When I look up what wait does, I see that:
Additional elements may appear at the end of the return value from wait. An optional fifth element identifies a class of information. Currently, the only possible value for this element is CHILDKILLED in which case the next two values are the C-style signal name and a short textual description.
The assert() call uses abort() to terminate the process if the assertion fails, and that shows up as an exit via SIGABRT.
set result [wait]
if {[lindex $result 4] eq "CHILDKILLED"} {
if {[lindex $result 5] eq "SIGABRT"} {
# assertion failed, or other abort
} else {
# other signal
}
} else {
# normal exit, may be with conventional error
}
Error handling can definitely be fiddly!
yesterday I got a very easy task, but unfortunatelly looks like i can't do with a nice code.
The task briefly: I have a lot of parameters, that I want to ask with whiptail "interactive" mode in the installer script.
The detail of code:
#!/bin/bash
address="192.168.0.1" # default address, what the user can modify
addressT="" # temporary variable, that I want to check and modify, thats why I don't modify in the function the original variable
port="1234"
portT=""
... #there is a lot of other variable that I need for the installer
function parameter_set {
$1=$(whiptail --title "Installer" --inputbox "$2" 12 60 "$3" 3>&1 1>&2 2>&3) # thats the line 38
}
parameter_set "addressT" "Please enter IP address!" "$address"
parameter_set "portT" "Please enter PORT!" "$port"
But i got the following error:
"./install.sh: line: 38: address=127.0.0.1: command not found"
If I modify the variable to another (not a parameter of function), works well.
function parameter_set {
foobar=$(whiptail --title "Installer" --inputbox "$2" 12 60 "$3" 3>&1 1>&2 2>&3)
echo $foobar
}
I try to use global retval variable, and assign to outside of the function to the original variable, it works, but I think it's not the nicest solution for this task.
Could anybody help me, what I do it wrong? :)
Thanks in advance (and sorry for my bad english..),
Attila
It seems that your whiptail command is not producing anyoutput because of the redirections. So the command substitution leaves the value empty. Try removing them. Also it's better to save the new value to a local variable first:
parameter_set() {
local NAME=$1
local NEWVALUE=$(whiptail --title "Installer" --inputbox "$2" 12 60 "$3")
export $NAME="$NEWVALUE"
}
I've defined a function hello in fishshell:
function hello
echo Hello
end
And save it:
funcsave hello
If I want to delete it, I can delete the file ~/.config/fish/functions/hello.fish.
Is there any other way to do it? (like built-in funcdel or funcrm)
No, there isn't any builtin to remove the file, but you can use:
functions --erase hello
or
functions -e hello
to erase the function definition from the current session.
See also
Documentation
I created another fish function for that
function funcdel
if test -e ~/.config/fish/functions/$argv[1].fish
rm ~/.config/fish/functions/$argv[1].fish
echo 'Deleted function ' $argv[1]
else
echo 'Not found function ' $argv[1]
end
end
The above solution of functions -e hello only deletes hello in the current session. Open another terminal, and the function is still there.
To delete the function in a persistent way, I had to resort to deleting the file ~/.config/fish/functions/hello.fish directly. Up till now, I do not know of another way that does deleting in a persistent way.
a more complete, self defined (and quiet) self-crafted solution, inspired by #Kanzee's answer (copy to file ~/.config/fish/functions/funcdel.fish):
function funcdel --description 'Deletes a fish function both permanently and from memory'
set -l fun_name $argv[1]
set -l fun_file ~/.config/fish/functions/$fun_name.fish
# Delete the in-memory function, if it exists
functions --erase $fun_name
# Delete the function permanently,
# if it exists as a file in the regular location
if test -e $fun_file
rm $fun_file
end
end
I combined the answer from #hoijui with some code from the function funcsave, so you can delete more than one function at once:
function funcdel --description 'Deletes a fish function both permanently and from memory'
set cf (status function)
set -l options 'h/help'
argparse -n funcdel $options -- $argv
or return
# should create a manpage
if set -q _flag_help
__fish_print_help cf
return 0
end
if not set -q argv[1]
printf (_ "%ls: Expected at least %d args, got only %d\n") $cf 1 0
return 1
end
set -l retval 0
for funcname in $argv
set -l funcfile $__fish_config_dir/functions/$funcname.fish
# Delete the in-memory function, if it exists
functions --query -- $funcname
if test $status -eq 0
functions --erase -- $funcname
printf (_ "%s: function %s removed from session\n") $cf $funcname
else
printf (_ "%s: Unknown function '%s'\n") $cf $funcname
end
# Delete the function permanently,
# if it exists as a file in the regular location
if test -e $funcfile
rm $funcfile
printf (_ "%s: %s deleted\n") $cf $funcfile
else
printf (_ "%s: %s not found\n") $cf $funcfile
end
end
return $retval
end
I have a unix script in which I am calling functions.
I want the function should return immediately if any of the command failed in between.
But checking $? after every command I can not do. Is there any other way to do this.
Maybe running the script from a file line by line (as long of course as each of your functions are one line long).
Maybe the following script can be a starting point:
#!/bin/sh
while read l
do
eval "$l || break"
done <<EOF
echo test | grep e
echo test2 | grep r
echo test3 grep 3
EOF
This is another idea after my previous answer. It works with bash script and requires your functions to be quite simple (pipes may cause some issues):
#!/bin/bash
set -o monitor
check() {
[ $? -eq 0 ] && exit
}
trap check SIGCHLD
/bin/echo $(( 1+1 ))
/bin/echo $(( 1/0 ))
/bin/echo $(( 2+2 ))
Furthermore: functions need to be external command (this is why I use /bin/echo rather than echo). Regards.
Consider this code:
set status [catch {eval exec $Executable $options | grep "(ERROR_|WARNING)*" ># stdout} errorMessage]
if { $status != 0 } {
return -code error ""
}
In case of errors in the child process, they are outputted in the stdout. But if there are no errors in the child process, the status value still non-zero. How avoid this?
Also is there are some ways to use fileutil::grep instead of bash grep?
In case of errors in the child process, they are outputted in the stdout. But if there are no errors in the child process, the status value still non zero. How avoid this?
There's no connection between writing something to any file descriptor (including the one connected to the "standadrd error stream") and returning a non-zero exit code as these concepts are completely separate as far as an OS is concerned. A process is free to perform no I/O at all and return a non-zero exit code (a somewhat common case for Unix daemons, which log everything, including errors, through syslog), or to write something to its standard error stream and return zero when exiting — a common case for software which write certain valuable data to their stdout and provide diagnostic messages, when requested, to their stderr.
So, first verify your process writes nothing to its standard error and still exits with non-zero exit code using plain shell
$ that_process --its --command-line-options and arguments if any >/dev/null
$ echo $?
(the process should print nothing, and echo $? should print a non-zero number).
If the case is true, and you're sure the process does not think something is wrong, you'll have to work around it using catch and processing the extended error information it returns — ignoring the case of the process exiting with the known exit code and propagating every other error.
Basically:
set rc [catch {exec ...} out]
if {$rc != 0} {
global errorCode errorInfo
if {[lindex $errorCode 0] ne "CHILDSTATUS"} {
# The error has nothing to do with non-zero process exit code
# (for instance, the program wasn't found or the current user
# did not have the necessary permissions etc), so pass it up:
return -code $rc -errorcode $errorCode -errorinfo $errorInfo $out
}
set exitcode [lindex $errorCode 2]
if {$exitcode != $known_exit_code} {
# Unknown exit code, propagate the error:
return -code $rc -errorcode $errorCode -errorinfo $errorInfo $out
}
# OK, do nothing as it has been a known exit code...
}
CHILDSTATUS (and the errorCode global variable in general) is described here.