How to Write and Debug Shell Script

Here in this article we will try to look at the basic steps that we can follow to write and debug a shell script.
Test Environment
- Fedora 41 server
A Shell script is basically a set of commands grouped together that can accomplish a particular task. This task can be something that you want to execute once, repeatedly or on a particular schedule.
Use Cases
- Renaming File extension:
- We have a bunch of files that you want to rename by appending a particular timestamp to filename. We can write a script that will take each file and rename it by appending timestamp.
- Disk Usage Monitoring:
- Disk usage monitoring is a continuous task which you want to execute to check if the Disk usage has reached a particular threshold. We can write a script to monitor the disk usage contiuously by writing a script.
- Log Management:
- We might be tasked to backup log files everyday from local system to a cloud storage for archival. We can write a script that runs on a particular schedule such as every morning to take the logs and transfer it to cloud storage for archival.
- Daemons and Service Startup:
- Linux OS internally trigger a lot of scripts that execute as per their defined order to Startup or Shutdown the System.
We can write shell scritps using any plain text editor such as vim, emacs, gedit, dtpad etc. We can also use popular code editors such as vscode to write the shell scripts which provides with inbuilt features syntax and error checking.
Writing our first Shell Script
- Identify the Editor you want to use to write your script
- Provide a name to your script preferably ending with .sh extension which defines its purpose clearly.
- Check if your script name conflicts with any existing command in the shell. which and whereis are some of the commands that we can use for this purpose
- Provide execute permission for the script
- Execute script
As mentioned previously a script is nothing but a set of commands. These commands can be shell functions, shell built-ins, unix commands and other scripts.
Let’s create a folder on our local system named “bashing” that will hold our scripts and write our first script named “hello.sh”.
admin@fedser:bashing$ cat hello.sh
#!/bin/bash
echo "Hello Bash!!"
So here we have used vim editor to write our first script “hello.sh”. The name of script precisely defines its purpose clearly which is greeting with a message. Also the script name is ending with “.sh” extension so that it can identified quickly from other different types of files such as excel, text, json, yaml or other file types.
Now that we have our script ready let us try to each line in the script that we have written.
The first line of a shell script should identify the interpreter that will be used to parse and execute this script. In our script it is “#!/bin/bash”. This line is also popularly called as **shebang** line. Every linux system usually comes with this bash shell. We can identify the path of this bash interpreter as follows.
Let us try to use “which” and “whereis” command to identify the bash interpreter path. As you can see below from the output the bash shell is available at “/usr/bin/bash”.
admin@fedser:bashing$ which bash
/usr/bin/bash
admin@fedser:bashing$ whereis bash
bash: /usr/bin/bash /usr/share/man/man1/bash.1.gz /usr/share/info/bash.info.gz
The “/bin” folder is a symoblic link to /usr/bin, So “/usr/bin/bash” is basically the same as “/bin/bash” which we are using in our script as the first line.
admin@fedser:bashing$ ls -ld /bin
lrwxrwxrwx. 1 root root 7 Jul 21 2023 /bin -> usr/bin
Now let us try to understand about the “echo” command. “echo” is a command used to display a line of text. It is provided by the “coreutils” package on the linux system as shown below. The coreutils package provides a set of basic GNU tools commonly used in shell scripts such as “cat”, “date”, “rm” etc.
admin@fedser:bashing$ rpm -qf /usr/bin/echo
coreutils-9.3-7.fc39.x86_64
Before we go ahead to execute our script we need to ensure that we do not have any script with the same name in our bash shell which will conflict with our script. Here we will again use “which” or “whereis” command to check for this conflict.
admin@fedser:bashing$ which hello.sh
/usr/bin/which: no hello.sh in (/home/admin/.local/bin:/home/admin/bin:/usr/lib64/ccache:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/home/admin/middleware/stack/glassfish7/bin:/home/admin/middleware/stack/glassfish7/glassfish/bin:/home/admin/middleware/stack/glassfish7/bin:/home/admin/middleware/stack/glassfish7/glassfish/bin)
As per the above output our script is not conflicting with any other script with the same name in the bash shell. So now we are good to proceed and execute our script.
But before executing our script we need to provide execute permissions to our script. We can do so using the “chmod” command as shown below.
admin@fedser:bashing$ chmod 744 hello.sh
admin@fedser:bashing$ ls -ltr hello.sh
-rwxr--r--. 1 admin admin 33 Aug 25 05:08 hello.sh
In order to execute our script we need to either provide the complete path to the script file for execution or switch to the directory where the script exists and execute it as shown below.
admin@fedser:bashing$ ./hello.sh
Hello Bash!!
The scripts folder path can also be added to the $PATH environment variable so that we can execute our script directly by providing its name rather than providing the completed directory path or switching to the scripts directory to execute it.
A script can also be executed explicitly by a specific shell interpreter. This is generally done if we want to obtain special behavior, such as checking if the script works with another shell or printing traces for debugging.
admin@fedser:bashing$ bash hello.sh # Executes the script in a subshell of the current bash shell
Hello Bash!!
admin@fedser:bashing$ sh hello.sh # Executes the script in sh subshell of the current bash shell
Hello Bash!!
admin@fedser:bashing$ bash -x hello.sh # Executes the script with xtrace mode enabled
+ echo 'Hello Bash!!'
Hello Bash!!
admin@fedser:bashing$ source hello.sh # Executes the script in the current shell
Hello Bash!!
NOTE: -x option in bash helps enable xtrace mode usually used to debug the script execution
Debugging Shell Scripts
It is important to understand how we can debug the shell scripts if its not providing us with the expected results. The most common way to debug a shell script is to enable trace mode in the subshell and execute the shell script using the “-x” option as shown in above section.
This will run the entire script in debug mode by printing each command that is getting executed to standard output after the commands have been expanded but before they are executed.
Let us try to modify our existing to define a variable and use it to generate a custom greeting message as shown below.
Here we have defined a variable “name” with value “novicejava1”. In order to use this variable’s value we need to refer it using the “$” sign as shown below in the “echo” command. The “{}” are optional but if included provides a more readable script.
admin@fedser:bashing$ cat hello.sh
#!/bin/bash
name="novicejava1"
echo "Hello ${name}!!"
Now let’s try to execute our script in debug mode and see if the commands are getting printed on the standard output along with expansion before getting executed.
admin@fedser:bashing$ bash -x hello.sh
+ name=novicejava1
+ echo 'Hello novicejava1!!'
Hello novicejava1!!
Let’s say if you want to debug only a part of the script which you feel may be misbehaving you can use the bash built-in command “set” to enable and disable debugging only a particular block of the script by wrapping that block in between “set -x” (ie. enable debugging) and “set +x” (ie. disable debugging). Here is the updated script with debugging enabled for a part of the script.
admin@fedser:bashing$ cat hello.sh
#!/bin/bash
name="novicejava1"
set -x
echo "Hello ${name}!!"
set +x
Now let’s see the script execution in action, here we will need to execute the script in standard way rather than in debug mode. As we can see the standard output only shows commands wrapped in between “set” built-in command.
admin@fedser:bashing$ ./hello.sh
+ echo 'Hello novicejava1!!'
Hello novicejava1!!
+ set +x
With tracing enabled you would be able to see what data is getting passed into each command. You can as well insert “echo” commands to print the values for each variable that you are using in a command before your buggy part of the script to display the content of variables at different stages in the script, so that flaws can be detected.
Hope you enjoyed reading this article. Thank you..
Leave a Reply
You must be logged in to post a comment.