In this lesson, you will learn about the options built into Bash that will help you find and fix errors in your shell scripts.
Learning Objectives
- Learn a range of commands used or debugging.
- Use variables to control the behavior of your shell scripts and help you in debugging.
- Learn how to manually debug a script.
- Understanding syntax highlighting and how that can help you avoid common mistakes.
- Learn about a special Bash variable that you can use to give you valuable information about what is happening inside of your scripts.
- Learn about the differences between Windows and Linux file types and the problems you may encounter if you plan to use multiple operating systems to create scripts.
Intended Audience
This course is intended for anyone looking to learn more about bash scripting and shell programming.
Prerequisites
To get the most out of this course, you should have some basic knowledge of the command line, but it's not essential.
In this lesson you will learn how to use variables to control the behavior of your shell scripts and help you in debugging. You'll also learn how to manually debug a script. We'll also talk about syntax highlighting and how that can help you avoid common mistakes. You'll learn about a special Bash variable that you can use to give you valuable information about what is happening inside of your scripts. Finally, you'll learn about the differences between Windows and Linux file types and the problems you may encounter if you plan to use multiple operating systems to create scripts.
Many times using -x, -e and -v is sufficient, but if you want a bit more control around debugging, you can create your own code to do it. One method I personally use is to create a variable called DEBUG. I set this to true if I'm currently debugging or false if I'm not. I'm using the Bash built-in Booleans of true and false. If you want to use a Boolean, do not quote them. If you were to quote true, for example, then it will act as a string. That's fine, and that's one way to do it, but then you'll have to use string comparisons later on in your script. Using Boolean simplifies testing for the debug condition in your script.
Here's a simple example of using a DEBUG variable with a Boolean value. You can test if DEBUG is set to true by using an if statement. Since debug is true, the if statement is true and echo "Debug mode ON." is executed. If you were to set DEBUG equals to false, then the if statement would be false and the else block would be executed. You can use the DEBUG variable in conjunction with ANDs or ORs. Remember that the double ampersand means AND. It will only allow commands to be executed after it if the return code of the proceeding command was zero. The true Boolean always returns a zero exit status. So for the first example, DEBUG is set to true. Since that is a zero exit status, the echo "Debug mode ON." command is executed. You can use this pattern to execute certain commands when DEBUG is set to true. The double pipe symbol represents OR. If the command proceeding an OR returns a non-zero exit status, then the command following the OR will be executed. The false Boolean always returns a non-zero exit status. So, for the second example, DEBUG is set to false. Since that is a non-zero exit status, the echo "Debug mode OFF." command is executed. You can use this type of pattern to skip the execution of certain commands when DEBUG is set to true. If DEBUG succeeds, then what follows the OR will not be executed. This can prove to be useful when you are in the process of writing a shell script. You may not want or need to execute some commands that will repeat work already performed or take a long time to run, for example. This can speed up the creation of your new scripts. So it's good for debugging and writing scripts.
Another thing you can do is set the value of the DEBUG variable to echo. Next, place $DEBUG before each line in your script. If DEBUG is set to echo, then whatever would normally have been executed is simply echo to the screen. In the first example, the output to the screen is simply ls because what gets executed is actually echo ls. In the second example, the DEBUG line has been commented out. This causes the value of DEBUG to be nothing. That leaves the remainder of the line, which is a space followed by ls. That means the ls command will execute. Again, this is another simple way to turn to bugging on and off in your scripts.
You can even create your own debug function and add whatever you would like around each command or choose not to execute the command if you wish. Use the special variable $@ inside your function to access everything passed to the function.
In this example, the command that will be executed is simply echo to the screen and then it executes that command. Of course, you could do something more complicated and useful. Maybe you could write this information to a log file and include timestamps showing exactly when each command was executed. Sometimes I simply open up a second terminal window and go through the shell script one line at a time. I will copy and paste the first line, then the second one at a time on the command line until I find where the problem is. Remember that you can use this in conjunction with set -x or any other option you might think will be helpful.
Many times bugs or errors in the shell script are due to a simple syntax error; maybe you mistyped something or you forgot to include a closing bracket or a closing quote, for example. These simple mistakes can be caught before you even try to execute your script if you're using an editor with syntax highlighting. Two of the most commonly used editors on the Linux system are Vim and Emacs. By default, they both have syntax highlighting. I've included a couple of screenshots from those editors to show you what this looks like. Of course, there are plenty of other editors with highlighting support. You'll have to check the documentation for your particular editor to see if shell script highlighting is supported, and if so, how to enable it.
You may be familiar with the PS1 environment variable. It controls what is displayed as your command prompt. There are other variable similar to this one. The one that is valuable for shell script debugging is PS4. PS4 is expanded and displayed before each command during an execution trace. That means that when you're using the set -x or the -x option for your Bash script, the contents of PS4 are printed to the screen. By default, this is just a plus sign. However, we can explicitly set the PS4 variable. Bash includes several built-in variables such as BASH_SOURCE, which is the name of the script itself, LINENO, which is the line number of the script, et cetera. Here is an example of using the PS4 variable. This script sets PS4 to the plus sign, then a space, then the name of the script by using the built-in variable BASH_SOURCE. Next to it include another space followed by a colon and yet another space. Finally we display the line number with a LINENO variable, use a space, a colon, and finally another space. You can see that when the script gets executed the output changes after the PS4 variable is set. This can make the output clearer. For example, if we had a problem with the echo command, we know we can edit the script and head straight for line four.
Here's a more advanced example. It just includes more information such as the function name. I included one line of sample output here. That output is line four from inside the debug function. You can see how the PS4 variable allowed us to print the function name using the built-in variable FUNCNAME. Plain text files, like we are working with for shell scripts, contain a control character to represent the end of a line. For Unix and Linux systems, the control character representing a new line is a line feed. DOS or Windows systems actually use two characters to represent a new line, a carriage return and a line feed. If you've ever created a text file on a Linux system and sent it to someone that is using a Windows system, it may very well display as one long line on their computer. This is due to the lack of carriage returns. If you were to do the opposite, say, create a text file on a Windows system and open it on a Linux system, there will be additional characters, specifically carriage returns in that file. The problem in this situation is that, when you display the contents of the file to the screen, you will not see the additional carriage returns. For example, if you ran cat script.sh and that file had carriage returns in it, you wouldn't see them. You would have to run cat -v script.sh to display non-printable characters like carriage returns. Carriage returns are represented by the caret symbol followed by an uppercase M. If you were to execute a file that contains carriage returns you'll probably run into an error like this: /bin/bash caret M, no such file or directory. The carriage return is being treated as part of the file name. In order to get this script to run, we need to remove the carriage returns. This situation is another reason why you should always start your scripts with a shebang. If you didn't have the shebang line, the commands would be executed using your current shell. This can lead to some strange things happening. The script might work as expected for parts of it but totally fail at other parts due to carriage returns. If you start getting strange errors that make no sense, check to see if there are any carriage returns in your script. In addition to using the cat -v command to see if there are any carriage returns in a file, you can use the file command. If it finds the additional carriage returns, it will alert you to that fact.
To easily remove these characters, I recommend that you use the dos2unix utility. It may not be installed by default on your system, so you may have to manually install it if you need it. This little utility converts a DOS file or a file with carriage returns into a Unix file or a file without carriage returns. There's another utility which does the exact opposite, and it's called unix2dos. After you run the dos2unix command against your file, you'll find that the carriage returns are removed. You can confirm this by running the file command against the file. You'll notice that there are no warnings about CRLF line terminators. The common ways you end up with these unwanted carriage returns in your shell scripts is due to creating files on a Windows system and uploading those files to a Linux system.
If you want to create shell scripts on a Window system, see if your editor supports Unix-style line endings. If it does support this option, simply turn it on. Also, some editors can convert between DOS and Unix-style file types. You can also end up with unwanted characters in your shell scripts if you copy some texts from your Windows computer and paste it into your PuTTY window. Even if you're using an all-Linux system, the copy and paste action can still get you. For example, if you copy some texts from a web page that has carriage returns and then paste that into a text file, that file may contain those carriage returns.
In this lesson you learned how to use variables to assist you in debugging and creating shell scripts. You also learned that it can be easier to catch mistakes if you're using an editor with syntax highlighting enabled. You learned about the PS4 variable and how to use that in conjunction with an xtace. Finally, you learned about the challenges of switching back and forth between Windows and Linux, and more importantly, how to catch and correct errors caused by carriage returns.
Jason is the founder of the Linux Training Academy as well as the author of "Linux for Beginners" and "Command Line Kung Fu." He has over 20 years of professional Linux experience, having worked for industry leaders such as Hewlett-Packard, Xerox, UPS, FireEye, and Amazon.com. Nothing gives him more satisfaction than knowing he has helped thousands of IT professionals level up their careers through his many books and courses.