# Exit status **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. ## Echo 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 ```c /* Compile: gcc main.c -o exit_status Shell: > ./exit_status 17 > echo $? 17 > echo $? 0 */ #include 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. ## Constants The standard C library header [[stdlib.h|]] provides the following the following constants two constants: - `EXIT_SUCCESS` - `EXIT_FAILURE` You can find them in `/usr/include/stdlib.h` with grep command (-A means "lines after", -B means "lines before", -e means "expression") ```c // $ 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 ```c // 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; } ``` ## Why is 0 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! ```c 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 ```c #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. ```bash $ 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 ``` ## Exit status: 130 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.