Intermediate Shell Scripting in Linux
Powerful way to perform repetitive tasks
Recap
Welcome to my second blog post on shell scripting! Before we dive into more advanced topics, I'd like to take a moment to revisit my first blog post on the basics of shell scripting. In that post, I covered essential concepts such as command-line arguments, variables, and control structures. If you haven't had a chance to read it yet, I highly recommend starting there before diving into this one.
To read my first blog post on shell scripting, click here.
Conditions
The general syntax for using conditions in a shell script is given below:
# if statements
if [ //some condition ]
then
#If the condition is valid, do something
fi
#if - else statements
if [ //some condition ]
then
#If the condition is valid, do something
else
#If the condition fails, do something
fi
# Conditional statements with multiple branches using if-elif-else
if [ //some condition ]
then
#If the condition is valid, do something
elif [ //some condition ]
then
#If the condition is valid, do something
elif [ //some condition ]
then
#If the condition is valid, do something
else
#If all the condition fails, do something
fi #The 'fi' keyword is used to close the conditional block
Examples
Run the command:
bash myscript.sh 5
ans=$1 # Assign the first command line argument to a variable called ans if [ $ans = 5 ] # Check if the value of ans is equal to 5 then # If the condition is true, execute the following command echo "User entered 5" else # If the condition is false, execute the following command echo "User didn't enter 5" fi # End the if statement
Run the command:
bash myscript.sh 8
ans=$1 # Assign the first command-line argument to a variable named "ans" if [ $ans = 5 ] # Check if $ans is equal to 5 then echo "User entered 5" # If so, print this message to the console elif [ $ans = 6 ] # If $ans is not equal to 5, check if it's equal to 6 then echo "User entered 6" # If so, print this message to the console else echo "User entered something else" # If $ans is not equal to 5 or 6, print this message fi # End of if-elif-else statement
if [ "abc" = "abc" ] # Check if "abc" is equal to "abc" then echo "abc is equal to abc" # If so, print this message to the console else echo "abc is not equal to abc" # If not, print this message to the console fi # End of if-else statement
if [ "bbc" != "abc" ] # Check if "bbc" is not equal to "abc" then echo "bbc is not equal to abc" # If so, print this message to the console else echo "bbc is equal to abc" # If not, print this message to the console fi # End of if-else statement
if [ 5 -eq 4 ] # Check if 5 is equal to 4 then then echo "5 is equal to 4" # print 5 is equal to 4 to the console else echo "5 is not equal to 4" #otherwise, print 5 is not equal to 4 to the console fi # end the if-else statement
# Check if 5 is not equal to 4 if [ 5 -ne 4 ] then # If the condition is true, print "5 is not equal to 4" echo "5 is not equal to 4" else # If the condition is false, print "5 is equal to 4" echo "5 is equal to 4" fi
# Check if 5 is greater than 4 if [ 5 -gt 4 ] then # If true, print the following message to the console echo "5 is greater than 4" else # If false, print the following message to the console echo "5 is not greater than 4" fi
# Check if 5 is less than 8 if [ 5 -lt 8 ] then # If true, print the following message to the console echo "5 is less than 8" else # If false, print the following message to the console echo "5 is not less than 8" fi
# Check if 5 is less than 3 and also less than 10. Both the conditions has to satisfy if [[ 5 -gt 3 && 5 -lt 10 ]] then # If true, print the following message to the console echo "5 is greater than 3 and less than 10" fi # Check if 5 is less than 3 or less than 10. If any one of them satisfies then whole condition satisfies if [[ 5 -gt 3 || 5 -lt 10 ]] then # If true, print the following message to the console echo "Either 5 is greater than 3 or less than 10" fi
# This snippet is same as the above one. The use of brackets in the if condition is different here. if [ 5 -gt 3 ] && [ 5 -lt 10 ] then echo "5 is greater than 3 and less than 10" fi if [ 5 -gt 3 ] || [ 5 -lt 10 ] then echo "Either 5 is greater than 3 or less than 10" fi
For Loop
Loops are useful when running the same command multiple times with different argument values. Like, creating a bunch of ten files.
Use cases:
Execute a command or a set of commands many times.
Iterate through files.
Iterate through lines within a file.
Iterate through the output of a command.
Examples
Use case: Execute a command or a set of commands many times.
Create 5 files: The variable 'file' will be replaced by the file name and the commands that will be executed are 'touch file1', 'touch file2' etc.
for file in file1 file2 file3 file4 file5 do touch $file done
Use case: Iterate through lines within a file.
Suppose you have a file in the current working directory that contains these lines:
Hello World
From Visual Studio Code
:)
When you run this loop it will read each line of the file and will print it. In this program, each line of the file will be stored in a variable 'file' and will be printed.
for file in $(cat myfile.txt) do echo $file done
A similar use case for installing a bunch of packages.
for pkg in $(cat packages.txt) do sudo apt install $pkg -y done
A similar use case for printing numbers.
for (( i = 0 ; i < 10 ; i++ )) do echo $i done for i in {0..9} do echo $i done
Use case:
Iterate through the output of a command.
Iterate through files.
Print the line count of each file in the current working directory.
for file in $(ls) do echo Line count of $file is $(cat $file | wc -l) done
While Loop
Use Cases:
Repetitively run a command or a group of commands until a certain condition is met or becomes true.
Create infinite loops.
Menu-driven programs.
Examples
Use case: Repetitively run a command or a group of commands until a certain condition is met or becomes true.
Until and unless the user enters 5 the program will not exit.
while [ true ] # start an infinite loop do read -p "Enter 5 to exit: " num # prompt user to enter a number if [ $num = 5 ] # check if the user entered 5 then break # exit the loop if user entered 5 fi done # end of loop
Use Case: Create infinite loops.
This program prints
Hello :)
infinite times. ctrl+c to exit.while [ true ] # start an infinite loop do echo "Hello :)" # print "Hello :)" to the console done # end of loop
Use Case: Menu-driven programs.
A basic calculator that exits when the user enters 5.
# input two numbers and choose between different arithmetic operations. while [ true ] do echo "==========" echo "Calculator" echo "==========" read -p "Enter first number: " n1 read -p "Enter second number: " n2 echo "Enter 1 for addition" echo "Enter 2 for subtraction" echo "Enter 3 for multiplication" echo "Enter 4 for division" echo "Enter 5 to exit" read -p "Enter choice: " ch if [ $ch -eq 1 ] then echo $((n1 + n2)) # addition operation elif [ $ch -eq 2 ] then echo $((n1 - n2)) # subtraction operation elif [ $ch -eq 3 ] then echo $((n1 * n2)) # multiplication operation elif [ $ch -eq 4 ] then echo $((n1 / n2)) # division operation else break # exit the loop if user enters 5 or any other value fi done
Print numbers from 1 to 9
# Set `i` to 1 i=1 # Continue running the loop until `i` becomes greater than or equal to 10 while [ $i -lt 10 ] do # Echo the current value of `i` echo $i # Subtract 1 from `i` and update the `i` variable i=`expr $i - 1` done
Until Loop
Until Loop executes a set of instructions repeatedly based on some boolean expression. The set of instructions is executed only while the value of the boolean expression or condition is false. As soon as the result of the boolean expression evaluates to true the loop stops. Although similar to a while loop, an until loop executes the set of instructions as long as the condition remains false.
Examples
Print numbers from 1 to 10
# Set `i` to 1 i=1 # Continue running the loop until `i` becomes greater than 10 until [ $i -gt 10 ] do # Echo the current value of `i` echo $i # Add 1 to the current value of `i` and update the `i` variable i=`expr $i + 1` done
Switch Case
This helps to avoid multiple if - else statements.
A simple calculator program designed using a switch case.
# Infinite loop until user chooses to exit (option 5)
while [ true ]
do
# Displaying the Calculator banner
echo "=========="
echo "Calculator"
echo "=========="
# Prompts user to enter two numbers
read -p "Enter first number: " n1
read -p "Enter second number: " n2
# Displaying options for the operations
echo "Enter 1 for addition"
echo "Enter 2 for subtraction"
echo "Enter 3 for multiplication"
echo "Enter 4 for division"
echo "Enter 5 to exit"
# Prompts user to choose an option
read -p "Enter choice: " ch
# Perform an arithmetic operation based on the user's choice
case $ch in
1) # Addition
echo $((n1 + n2))
;;
2) # Subtraction
echo $((n1 - n2))
;;
3) # Multiplication
echo $((n1 * n2))
;;
4) # Division
echo $((n1 / n2))
;;
5) # Exit
break
;;
*) # Invalid input
echo "Invalid choice"
;;
esac
done
Shebang
A shebang statement, also known as a hashbang, is a special line of code that specifies the interpreter to be used when executing a shell script. It is typically included at the beginning of the script and starts with the characters '#!' followed by the path to the interpreter. This allows the shell to automatically select the correct interpreter to execute the script with, avoiding errors caused by executing the script with the wrong interpreter.
Example:
#!/bin/bash
for val in {0..10}
do
echo $val
done
#!/bin/bash - This shebang statement includes '#!' followed by the interpreter on which the script will be executed. It will be executed in the Bourne-again shell or bash shell.
#!/bin/sh - This shebang statement will execute the script in the bourne shell.
There are many shells in Linux, but they do not work the same way or execute a particular instruction in the same way. Running the script below using the bash shell works perfectly fine and prints numbers from 0 to 10.
for val in {0..10}
do
echo $val
done
Scripts are interpreted by the shell environment mentioned in the shebang statement, located at the beginning of the script. For instance, the #!/bin/bash
shebang indicates that the script will run on the Bourne-again shell or bash shell. If the same script is executed using sh (Bourne shell) which does not support brace expansion, you may encounter errors or unexpected output. For example, if you use the {0..10}
expression to generate a sequence of numbers, the Bourne shell will not expand it into a loop and produce the output {0..10}
instead. To avoid such issues, always specify the appropriate shebang statement at the top of your scripts. This ensures that the script runs on the intended shell environment, irrespective of the environment you are executing it on.
Keep in mind that if you specify a shell explicitly while running a script, the shell mentioned within the script won't be used.
Run the command:bash myscript.sh
The script will always be executed through the bash shell irrespective of the shell mentioned inside the script.
Use cases
Although it is common to include the executable of a shell in a shebang statement, it is not mandatory. Instead of providing the executable for a shell, you can specify the executable of any other command, such as the 'cat' command. As long as the command is executable and installed on the system, running the script will execute the specified command and produce its output. However, it is necessary to specify an executable of some command.
#!/bin/cat
for val in {0..10}
do
echo $val
done
IMPORTANT POINTS ON SHEBANG
- There is no space between # and !
- It is good practice not to use space between #! and /bin/bash.
- #! has to be on the first line otherwise, it will be treated as a comment. It must not even contain a blank line before it.
Exit Codes
In Linux, every command returns an exit code or a return code indicating whether the command executed successfully or encountered an error.
The exit code can be 0 or greater than 0 depending on whether the command ran successfully or not.
If the value is 0 the command ran successfully or greater than 0 if it failed.
The exit code of the last executed command is stored in the special built-in variable '$?' in Linux.
The value of the variable gets updated each time you run a command.
We can check the exit code right after running a command to verify the status of the command run.
Run the command:
ls
Check the status:
echo $?
-> 0 (As the previous command ran successfully.)Run the command:
mkdirr dir
Check the status:
echo $?
-> Greater than 0 (As the previous command didn't ran successfully.)Run the command:
echo $?
-> 0 (As the previous echo command ran successfully.)
Functions
Functions are generally a block of code that performs a specific task. They make the code much cleaner. Functions also promote the reusability of code. Shell scripting follows the same mechanism when it comes to functions. It's important to declare functions before calling them to ensure their availability.
A general syntax:
function func_name() {
// do something
}
Examples
A simple program that calls a function 'add' and performs the addition of two numbers.
#!/bin/bash function add() { a=5 b=6 echo $(($a + $b)) } # Call the function add
This will prompt the user to enter two numbers, add them together, and then print the result to the console.
function add () { read -p "Enter first number: " a read -p "Enter second number: " b echo $(($a+$b)) } add
Here 3 and 5 are passed as arguments to the add function. 3 is stored in '$1' and 5 is stored in '$2'
function add() { echo $(($1 + $2)) } # Call the add function and pass in 3 and 5 as arguments add 3 5
The return keyword here is used to return a value from the functions. However, the returned value is stored in the '$?' variable which by default stores the exit codes.
function add() { return $(($1 + $2)) } # Call the add function and pass in 3 and 5 as arguments add 3 5 # Check the return value of the add function result=$? echo "The result is: $result"
If you do not want to store the returned value in the '$?' variable then use 'echo' instead of return. This way you can directly assign the returned value in your custom variable.
function add() { echo $(($1 + $2)) } # Call the add function and capture its output in the `sum` variable sum=$(add 3 5) # Print the value of the `sum` variable echo $sum
Arrays
Arrays are used to store a collection of values. Using too many variables can be confusing sometimes. In many programming languages, arrays are typically used to store values of the same type, but Bash arrays can hold values of different types. Additionally, like most programming languages, Bash arrays are zero-indexed, which means that the first element has an index of 0, the second element has an index of 1, and so on.
Initialization
There are multiple ways to initialize values in an array.
a[0]=5
a[1]=6
a[2]=8
a[3]=5
b=(54 87 98 11 236 45)
c=([0]=5 [1]=6 [2]=356 [3]=100)
d=([0]=ubuntu [1]=fedora [2]=centos [3]=linux [4]=1000)
Accessing values
Access all elements
echo ${a[@]} # Output - 5 6 8 5 echo ${c[*]} # Output - 5 6 356 100
Access a particular element
echo ${a[2]} # Output - 8 echo ${c[3]} # Output - 100 echo ${b[4]} # Output - 236
Access all elements from a particular index
echo ${a[@]:3} # Output - 5 echo ${b[@]:3} # Output - 11 236 45 echo ${c[@]:0} # Output - 5 6 356 100
Accessing in a Range:
echo ${ARRAYNAME[WHICH_ELEMENT/All_ELEMENTS]:STARTING_INDEX:COUNT_ELEMENT/COUNT_CHARACTERS}
echo ${a[@]:1:1} # Output - 6 // Consider all elements in the array, start from index 1 and count 1 element. echo ${b[@]:3:2} # Output - 11 236 // Consider all elements in the array, start from index 3 and count 2 elements. echo ${c[@]:0:3} # Output - 5 6 356 // Consider all elements in the array, start from index 0 and count 3 elements. echo ${d[1]:1:2} # Output - ed // Consider element at index 1 in the array, start from index 1 of the element and print 2 characters. echo ${d[4]:1:2} # Output - 00 // Consider element at index 4 in the array, start from index 1 of the element and print 2 characters.
Length of the array. '*' can be used in place of '@'
echo ${#a[@]} # Output - 4 echo ${#b[@]} # Output - 6 echo ${#c[@]} # Output - 4 echo ${#d[@]} # Output - 5
Length of an element at a particular index.
echo ${#a[0]} # Output - 1 (Length OF 5 - 1 Character) echo ${#b[2]} # Output - 2 (Length OF 98 - 2 Characters) echo ${#c[3]} # Output - 3 (Length OF 100 - 3 Characters) echo ${#d[2]} # Output - 6 (Length OF 'centos' - 6 Characters)
Conclusion
In conclusion, shell scripting is a powerful tool in the Linux environment that allows you to automate. Through this article, I have covered a range of essential topics such as conditions, loops, switch cases, shebang, exit codes, functions, and arrays. By mastering these concepts, you can create complex shell scripts that can save you time and effort by automating many tasks.
Whether you are a system administrator or a developer, understanding shell scripting is a valuable skill that can improve your productivity and efficiency.
Thanks for reading the blog. Connect with me on Twitter.