How to Trace Execution of Commands in Shell Script with Shell Tracing

In this article of the shell script debugging series, we will explain the third shell script debugging mode, that is shell tracing and look at some examples to demonstrate how it works, and how it can be used.

The previous part of this series clearly throws light upon the two other shell script debugging modes: verbose mode and syntax checking mode with easy-to-understand examples of how to enable shell script debugging in these modes.

Shell tracing simply means tracing the execution of the commands in a shell script. To switch on shell tracing, use the -x debugging option.

This directs the shell to display all commands and their arguments on the terminal as they are executed.

We will use the sys_info.sh shell script below, which briefly prints your system date and time, number of users logged in and the system uptime. However, it contains syntax errors that we need to find and correct.

#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;    
}
print_sys_info(){
echo "System Time    : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime  : $UPTIME
}
check_root
print_sys_info
exit 0

Save the file and make the script executable. The script can only be run by root, therefore employ the sudo command to run it as below:

$ chmod +x sys_info.sh
$ sudo bash -x sys_info.sh

Shell Tracing - Show Error in Script

From the output above, we can observe that, a command is first executed before its output is substituted as the value of a variable.

For example, the date was first executed and the its output was substituted as the value of the variable DATE.

We can perform syntax checking to only display the syntax errors as follows:

$ sudo bash -n sys_info.sh 

Syntax Checking in Script

If we look at the shell script critically, we will realize that the if statement is missing a closing fi word. Therefore, let us add it and the new script should now look like below:

#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi    
}
print_sys_info(){
echo "System Time    : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime  : $UPTIME
}
check_root
print_sys_info
exit 0

Save the file again and invoke it as root and do some syntax checking:

$ sudo bash -n sys_info.sh

Perform Syntax Check in Shell Scripts

The result of our syntax checking operation above still shows that there is one more bug in our script on line 21. So, we still have some syntax correction to do.

If we look through the script analytically one more time, the error on line 21 is due to a missing closing double quote (”) in the last echo command inside the print_sys_info function.

We will add the closing double quote in the echo command and save the file. The changed script is below:

#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time    : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime  : $UPTIME"
}
check_root
print_sys_info
exit 0

Now syntactically check the script one more time.

$ sudo bash -n sys_info.sh

The command above will not produce any output because our script is now syntactically correct. We can as well trace the execution of the script all for a second time and it should work well:

$ sudo bash -x sys_info.sh

Trace Shell Script Execution

Now run the script.

$ sudo ./sys_info.sh

Shell Script to Show Date, Time and Uptime

Importance of Shell Script Execution Tracing

Shell script tracing helps us identify syntax errors and more importantly, logical errors. Take for instance the check_root function in the sys_info.sh shell script, which is intended to determine if a user is root or not, since the script is only allowed to be executed by the superuser.

check_root(){
if [ "$UID" -ne "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}

The magic here is controlled by the if statement expression [ "$UID" -ne "$ROOT_ID" ], once we do not use the suitable numerical operator (-ne in this case, which means not equal ), we end up with a possible logical error.

Assuming that we used -eq ( means equal to), this would permit any system user as well as the root user to run the script, hence a logical error.

check_root(){
if [ "$UID" -eq "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}

Note: As we looked at before at the start of this series, the set shell built-in command can activate debugging in a particular section of a shell script.

Therefore, the line below will help us find this logical error in the function by tracing its execution:

The script with a logical error:

#!/bin/bash
#script to print brief system info
ROOT_ID="0"
DATE=`date`
NO_USERS=`who | wc -l`
UPTIME=`uptime`
check_root(){
if [ "$UID" -eq "$ROOT_ID" ]; then
echo "You are not allowed to execute this program!"
exit 1;
fi
}
print_sys_info(){
echo "System Time    : $DATE"
echo "Number of users: $NO_USERS"
echo "System Uptime  : $UPTIME"
}
#turning on and off debugging of check_root function
set -x ; check_root;  set +x ;
print_sys_info
exit 0

Save the file and invoke the script, we can see that a regular system user can run the script without sudo as in the output below. This is because the value of USER_ID is 100 which is not equal to root ROOT_ID which is 0.

$ ./sys_info.sh

Run Shell Script Without Sudo

Linux commands

sudo!! : Forgot to run a command with sudo? You need not re-write the whole command, just type “sudo!!” and the last command will run with sudo.

2. Python -m SimpleHTTPServer : Creates a simple web page for the current working directory over port 8000.

3. mtr : A command which is a combination of ‘ping’ and ‘traceroute’ command.

4. Ctrl+x+e : This key combination fires up, an editor in the terminal, instantaneously.

5. nl : Outputs the content of text file with lines Numbered.

6. shuf : Randomly selects line/file/folder from a file/folder.

7. ss : Outputs Socket Statistics.

8. Last: Want to know history of last logged in users? This command comes to rescue here.

9. curl ifconfig.me : Shows machine’s external IP Address.

10. tree : Prints files and folders in tree like fashion, recursively.

11. Pstree : Prints running processes with child processes, recursively.

13. stat : Shows the status information of a file as well as of a file system.

15. Pv : outputs simulating text, similar to hollywood movies.

16. Mount | column -t : Lists mounted file system, in nice formatting with specification.

17. Ctrl + l: clear shell prompt, instantaneously.

18. curl -u gmail_id –silent “https://mail.google.com/mail/feed/atom” | perl -ne ‘print “\t” if //; print “$2\n” if /(.*)/;’. This simple scripts, opens up, unread mail of an user, in the terminal itself.

19. screen : Detach and Reattach, long running process from a session.

20. file : Outputs information, regarding types of file.

21. id : Print User and Group Id.
22. ^foo^bar : Run last command with modification, without the need of rewriting the whole command again.

24. at : Run a particular command, time based.

25. du -h –max-depth=1 Command : Outputs the size of all the files and folders within current folder, in human readable format.

26. expr : Solve simple mathematical calculations from the terminal.

27. look: Check for an English word, from the dictionary, in case of confusion, right from the shell.

28. yes : continues to print a sting, till interrupt instruction is given.

29. factor: Gives all the possible factors of a decimal number.

30. ping -i 60 -a IP_address : Pings the provided IP_address, and gives audible sound when host comes alive.

31. tac : Prints content of a file, in reverse order.

32. strace : A debugging tool.

33. disown -a && exit Command : Run a command in background, even after terminal session is closed.

34. getconf LONG_BIT Command : Output Machine Architecture, very clearly.

35. while sleep 1;do tput sc;tput cup 0 $(($(tput cols)-29));date;tput rc;done & : The script outputs date and time on the top right corner of shell/ terminal.

36. convert : converts the output of a command in picture, automatically.

37. watch -t -n1 “date +%T|figlet” : Show animated digital clock at the prompt.

38. host and dig : DNS lookup utility.

39. dstat : Generates statistics regarding system resource.

40. bind -p : Shows all the shortcuts available in Bash.

41. Touch /forcefsck : Force file-system check on next boot.

42. lsb_release : Prints distribution specification information.

43. nc -ZV localhost port_number : Check if a specific port is open or not.

44. curl ipinfo.io : Outputs Geographical Information, regarding an ip_address.

45. find .-user xyz : Lists all file owned by user ‘xyz’

46. apt-get build-dep package_name: Build all the dependency, automatically while installing any specific package.

47. lsof -iTCP:80 -sTCP:LISTEN. The script, outputs all the service/process using port 80.

48. find -size +100M : This command combination, Lists all the files/folders the size of which is 100M or more.

49. pdftk : A nice way to concatenate a lot of pdf files, into one.

50. ps -LF -u user_name : Outputs Processes and Threads of a user.

51. Startx — :1 (This command creates another new X session).

top 6 files that eat up your space:

du -hsx * | sort -rh | head -6
  • Alt+Backspace: Deletes the previous word.
  • Alt+F: Skips ahead to the next space.
  • Alt+B: Skips back to the previous space.
  • Ctrl+U: Cuts all text up to the cursor.
  • Ctrl+K: Cuts all text after the cursor until end of line.
  • Ctrl+A: Moves the cursor to the start of line.
  • Ctrl+E: Moves the cursor to the end of line.