int main() {
return 0;
}
Just as functions in programming languages can have return values, programs in Unix also have return values, as the above example illustrates. We're not usually aware of the values returned by the commands we run, but they can be quite useful.
Put the above example into a file called success.c. Then compile it with gcc -o success success.c. Congratulations! You're a C programmer. You can run ./success to try it out, although you'll find that it does very little.
Now make a binary called failure, which returns 1 instead of 0. Run it, and you'll see that it behaves just like success did. At least, as far as you can tell.
Wait, what? Isn't 1 normally associated with truth and success, while 0 is associated with failure? Well, yes. But here's the thing: the designers of Unix figured there's only one kind of success, but many ways to fail. So they inverted the normal convention just in this case, defining 0 to be success, and nonzero values to indicate failure. Don't let that confuse you too much; mostly, it'll work the way you expect.
There are a couple of ways to make use of exit codes from the shell. The simplest are && and ||. I used those for years before I bothered to learn about if statements and for loops in shell scripting. They work like this:
./success && echo foo
./success || echo foo
./failure && echo foo
./failure || echo foo
They're called || and && because in C, there are "logical OR" and "logical AND" operators that let you say things like:
if (gun_unloaded && safety_engaged) { printf("Safe to put away.\n"); }
// this would be read "if gun_unloaded AND safety_engaged..."
or
if (gun_loaded || safety_off) { printf("Gun isn't safe yet.\n"); }
// this would be read "if gun_loaded OR safety_off..."
That's the normal way to use them, but it really doesn't explain what happens in the shell. You need one more factoid: somebody realized that the compiler can save a few instructions if it treats && and || as "short circuit" operators. That is, if gun_unloaded is false, there's no point checking whether safety_engaged is true, since we only proceed if both are true. Likewise, if gun_loaded is true, we know that we'll proceed regardless of whether safety_off is true.
So, still working in C, we can turn those variables into function calls and see for ourselves that the compiler correctly "short circuits" both operators:
#include
bool is_gun_unloaded() { return false; }
bool is_safety_engaged() { return true; } // this never gets called, since is_gun_unloaded returns false
main() {
if ( is_gun_unloaded() && is_safety_engaged() ) { printf("Safe to put away.\n"); }
}
And *that's* how it relates to the shell. When you say `./failure && echo foo`, the shell says "hm, failure failed, so don't bother to echo foo. And when you say `./success && echo foo`, it says "hm, success succeeded, so we need to find out if echo foo will succeed before we know whether the whole statement "./success && echo foo" is true or not. Now, we don't really care whether the whole statement evaluates to true or not. We just want to ensure that "echo foo" only gets run if success succeeded. We call that a "side effect" of evaluating the statement.
So, the way to think about it is that && lets you do the thing on the right only if the left hand side command succeeds, while || lets you do the thing on the right only if the left hand side fails. So:
ls / && echo yes, the root directory still exists
ls /sdfasdfasdflk || echo hm, having trouble finding /sdfasdfasdflk
You can quiet ls down so you don't actually have to listen to what it says, but only what it returns:
ls / >/dev/null 2>/dev/null && echo yup, still there
ls /bleh >/dev/null 2>/dev/null || echo nope, no /bleh either
But wait, there's more. You can also use the special varilable $? in bash to give you the actual numerical return value of the last command run:
ls /
echo $? # remember, 0 means success
ls /bleh
echo $?
Likewise, when you run a shell script, you can return the integer of your choice (modulo 256) using exit:
$ bash # start a subshell
$ exit 3 # leave the subshell
$ echo $? # the second copy of bash just terminated, so tell us its return value
This can be a useful way for your scripts to tell the scripts that called them whether they failed, and what kind of failures they had.
Challenge:
- use ping -w 1 -c 1 google.com to see if google is up. But don't show the output of ping, just print out a nice message saying so.
- write a script that grabs 1MB of data from /dev/urandom at a time and tells you if the word "foo" appears in it. Stop once you find one and indicate how many tries it took.