Exit status lets the user of your program know if the program ended in success or failure.
Commonly, 0 is success, any other value is failure (most commonly, failure is 1 – a bit counter intuitive at first!)
Your users can inspect it with echo $? after your program ends. Thanks to the exit status, your user can chain program with other programs using && and put your program in if [ ... ]; then ... fi statements when they write shell scripts.
When you run a program, the shell remembers the exit status in the $? variable.
You can inspect that variable with echo $?. The echo command is going to replace the value of $? with its own exit status (likely a success)
Here's a simple prog
/* Compile: gcc main.c -o exit_status Shell: > ./exit_status 17 > echo $? 17 > echo $? 0 */ #include <stdlib.h> int main(int argc, char *argv[]) { return (argc == 2 ? atoi(argv[1]) : EXIT_FAILURE); }
The program itself prints nothing – but the shell remembers the exit status of the last program in $? variable. The shell expands the variable, gives the value “17” to the echo commands, the echo commands prints “17”, and then returns the value 0 to the shell. The shell then remembers “0” in $? variable.
The standard C library header <stdlib.h> provides the following the following constants two constants:
EXIT_SUCCESSEXIT_FAILURE
You can find them in /usr/include/stdlib.h with grep command (-A means “lines after”, -B means “lines before”, -e means “expression”)
// $ grep -A 1 -B 3 -e EXIT_FAILURE /usr/include/stdlib.h /* We define these the same for all machines. Changes from this to the outside world should be done in `_exit'. */ #define EXIT_FAILURE 1 /* Failing exit status. */ #define EXIT_SUCCESS 0 /* Successful exit status. */
R 0 or 1
// Compile: gcc main.c -o my_cat // Run: // $ echo "Hello world!" > file.txt // $ ./my_cat file.txt // Hello world! int main(int argc, char *argv[]) { // Check arguments if (argc != 2) { fprintf(stderr, "Error: Expected one. argument"); return EXIT_FAILURE; } // Open file FILE *file = NULL; if ((file = fopen(argv[1], "r")) == NULL) { fprintf(stderr, "Error: Can't open file '%s' for reading", file); return EXIT_FAILURE; } // Read and print chars int ch; while ((ch = fgetc(file)) != EOF) { putchar(ch); } // Close file fclose(file); // Return success return EXIT_SUCCESS; }
In C programming language, zero is considered false. What's funny is that if you use EXIT_SUCCESS in an if-statement directly the if-block is skipped – because EXIT_SUCCESS is defined as 0!
if (EXIT_SUCCESS) { printf("This never prints..."); }
So why is EXIT_SUCCESS defined as 0?
That's because the original intent of exit status was
#define SUCCESS 0 #define FAILURE_ONE 1 #define FAILURE_TWO 2 ... #define FAILURE_LAST 255 int main() { if (/*... something goes wrong 1 ...*/) return FAILURE_ONE; if (/*... something goes wrong 2 ...*/) return FAILURE_TWO; return SUCCESS; }
But there are way more than 255 ways a program can fail – and it's much better if you get a descriptive error in stderr instead of a single number.
Think about it! If you forget a semicolon – would you rather the compiler tell where exactly? Or would you rather it tell you something cryptic “Error code: 233”?
That's why, when most programs fail, they print a detailed report in stderr but return EXIT_FAILURE which is defined as 1.
$ gcc main.c -o program main.c: In function ‘main’: main.c:5:26: error: expected ‘;’ before ‘}’ token 5 | printf("Hello world") | ^ | ; 6 | } | ~ $ echo $? 1
When a program is interrupted by a signal the exit status
exit_status = 128 + signal_number Ctrl + A = 128 Ctrl + B = 129 Ctrl + C = 130 ... and so on ...
When you cancel a program with Ctrl + C, you're sending SIGINT signal (“signal interrupt”) to the program which interrupts its execution and the value of $? is going to say 130.