The correct handling of return values made my life as a programmer much easier. That is the whole purpose of their creation. I can write code that will check itself and will notice if any step of any program failed or produced an undesired result, and act accordingly. Checking the return values allows a programmer to write incredibly robust programs.
I wanted to write a post about this, thinking in rookie programmers. I tried to make it as useful and easy to understand as possible.
echo is a UNIX/Linux command that will show a message in the screen. $? is a special variable that stores the return code of the last ran command. Together they are a powerful tool used for programming and systems administration.
A program should always return a value. Any coding standard should have these lines in it:
A successful program should return 0 upon correct completion.
An unsuccessful program should return a non-zero value.
For example, let’s write a shell script that creates an empty file named “file.txt” in /home/user1. The most basic version of this script would be something like this:
That’s it, just one line. Simple, uh? Well, maybe it is too simple. What if there is no free space in the file system? What if the user running the command does not have writing permission on that path? What if that path does not even exists? What if there is already a file there with the same name? Should we overwrite it?
The following is a shell script that is a little more robust (but still very simple). It completes the same task but it checks for more possible failure scenarios:
#!/bin/sh # Author: Alan Verdugo # # Return codes: # 0 - Great success! The file was created. # 1 - There is already a file with that name there. # 2 - We attempted to create the file but it was not created. # 3 - We attempted to create the file but we cannot write on that path. # 4 - The path itself does not exist. # Check if the file already exists... if [ -e /home/user1/file.txt ] then echo "ERROR: There is a file in that path with the same name!" exit 1 else # Great, the file does not exists. # Now let's check if the path actually exists. if [ -e /home/user1 ] then # Now check if we can write on this path. if [ -w /home/user1/ ] then # We go ahead and attempt to create the file. touch /home/user1/file.txt # Check if the file was created succesfully. if [ -e /home/user1/file.txt ] then echo "File created successfully!" exit 0 else echo "ERROR: File was unable to be created!" exit 2 fi else echo "ERROR: We cannot write on this path!" exit 3 fi else echo "ERROR: The path does not exists!" exit 4 fi fi
The “exit” command means to abort the program and return to the OS with the integer that follows the “exit” keyword. You may notice that the only successful scenario ends the program with “exit 0” and all the other (failed) scenarios end with “exit” and a positive integer. So, after the program ends, the programmer or sysadmin can easily check if the program succeeded or not. For example, something like this could happen:
$ ./return_codes.sh ERROR: The path does not exists! $ echo $? 4
In the example above, the script failed and it showed us a pretty message explaining why. It also returned the value 4. If our script is called by another script, that caller script can check easily if a non-zero value is returned (i.e. if the script failed). The following is an example of a successful execution:
$ ./return_codes.sh File created successfully! $ echo $? 0
If you play with the “stock” programs in a default UNIX/Linux installation, you can see this behaviour as well. For example:
$ date Mon Jul 27 12:13:46 CDT 2015 $ echo $? 0
$ find . -name helloWorld.sh $ echo $? 0
Wait! Why did the “find” command returned a zero?! It did not find the file we asked it to find! The reason is because the execution of the “find” program completed successfully. The fact that it was unable to find a file named “helloWorld.sh” is another matter entirely. In other words, this means the “find” program is telling us that it is functioning properly, it is not its fault that the file you try to find does not exist there. A nice thing to have in mind– Computers will do what you tell them to do, not always what you wish they will do.
Code like this makes our programs more robust, and can help us to avoid catastrophic failures and emergency calls late at night. Different programming languages and paradigms offer different ways to accomplish this same idea. The “try-catch” statements are good examples of this.
The truth is, you can write a program as complicated as you want. There are many ways to improve the previous script. You need to know when it is “sufficiently completed” and stop there. We could continue on and on with our little script and add fancy functionality like sending a failure report via email, creating the file in an alternative path if the first path does not allow it for some reason, etc. But let’s stop there for now.