Bash Scripting
Bash Scripting
It becomes very easy to control your whole os using GUI interfacing. But at a lot of situation you
w8ill expected to control the whole OS using CLI interface. When can this happen ?
Let's say we hosted our backend application on a AWS cloud server, now these server are
present in a very remote location, now if we have to interact with this server, then we need to
control the OS of this server from our machione. And here CLI will only help us as we won't be
having any GUI access to the server.
In CLI interface, we use softwares like iterm, terminal, CMD, power shell etc, which gives us an
interface to write some commands that will be running directly in our OS.
REPL Console
REPL stands for Read, Evaluate, Print, Loop. A repl console is a special type of console which
understand a particular programming language and every time we run it, it will expect us to add
one valid instruction to the console, it will then evaluate it's output, print the output and then go
back to the same same where it is expecting an input from us.
Languages like Python, ruby, JS all have their respective REPL consoles.
Even, the terminal which we see in our OS are REPL consoles, which understand bash
scripting language.
What is Shell ?
A shell in the context of computing and operating systems like Linux, is an interface that allows
you to access and interact services of your operating system. The primary function of shell is to
accept commands from user and then execute them.
What is Bash ?
Bash is a type of scripting language that helps us to interact with a linux shell. Bash stands for
Bourse Again Shell.
Using Bash, we can write linux command in CLI softwares like Iterm and terminal, and also
write end to end programmed scripts which can help us to automate a lot of things in our
computer.
echo "Ending...."
ls
Let's say we save the above code in a new script1.sh file. Now to run this file, we can use
the following command:
sh script1.sh
or
bash script1.sh
With both of these commands we can actually run the bash script and see output on our
screen.
Now in your machine there can be more than one shell scripting language available, like bash,
zsh etc. Sometimes the default scripting language is not bash, so that's why we have to
separately mention bash fileName to enable it to be run by bash script. Bash is an
modification of the sh shell script.
If we don't want to mention which scrripting language to use in order to run the file, and exepct
it to pick it from the code we can use shebang.
Shebang in scripts
Shebang looks like this: #! This shebang is added on the top of the script and then we can
mention that path of the scripting language we want to get executed by.
#!/bin/bash
echo "Ending...."
Here we have added some bash code and at the top of the file added #!/bin/bash . So we
passed the path of the interpreter which will be used to run the code, instead of picking the
default one from system.
Because we have mentioned the interpreter, we can run the file independently,
./script1.sh
But this command will give you error. Why ? Because the current script file is not executable.
When we say bash script1.sh then bash interpreter is directly executable in the terminal. To
make our file directly executable we can change it's permission.
chmod +x script1.sh
And now, we can run the script independently
./script1.sh
This command will start running the file, at the top because of shebang pick the interpreter as
bash and then run the remaining code with bash.
To create a variable, we just give variable name put an equals and then give it a value
threshold=80
This command creates a new variable threshold with value 80. Now if we want o use this
variable at any place, we have to prepend a $ before the variable name to access it's value.
In this statement, $threshold helps us to access a the value of the variable threshold.
Now to add a variable inside a bash script we can use the same syntax.
The variables which we create in a terminal, or in script will exist only in the same terminal
session, once you close your terminal or move to a new terminal window, those variables won't
be accessible.
Apart from variables created by us, there are some predefined variables as well like $USER ,
$PATH that are already present in every terminal session we create. These are predefined in
linux and serve some specific purpose but if we want we can change their value.
If we want to see all the inbuilt variables we can use the command :
env
This will list all the existing prebuilt variables for us.
Subshells
If we want to store the output of a linux command inside a variable we can do that using
subshell.
current_path=$(pwd)
Here for multiplication we have to use \ because * is considered for regex matching also in
shell.
In bash, we have the if keyword that can help us to put a conditional in place.
if [ condition ];then
# some algorithm
fi
So, any if block starts with the if keyword and ends with fi keyword and in between we
put the condition and algorithm to execute when condition is true.
For example:
#!/bin/bash
my_value=2000
Here in the conditionals square bracket we can use operators to prepare a condition which
evaluates to a boolean true or false.
If we want to put multiple conditions in place then we can also use elif .
#!/bin/bash
my_value=2000
if [ $my_value -gt 2000 ]; then
echo "The condition is true"
else
echo "The condition is false"
fi
Here the part command -v is an actual valid linux bash command, which takes input of a
command and tell the path from which command is executed if it exists.
For example if curl command is installed in your machine and we do
command -v curl
1. Arithmetic Operators
Arithmetic operators are used to perform basic mathematical operations.
2. Comparison Operators
Comparison operators are used to compare numbers or strings. These are commonly used in
conditional statements like if or while .
< Less than, in ASCII alphabetical order [ "abc" < "xyz" ] → true
> Greater than, in ASCII alphabetical order [ "xyz" > "abc" ] → true
3. Logical Operators
Logical operators are used to combine multiple conditions.
|| Logical OR `[ 5 - lt 3 ] || [ 5 -gt 3 ] `
! Logical NOT [ ! 5 -eq 3 ] → true
5. Assignment Operators
Assignment operators are used to assign values to variables.
6. Miscellaneous Operators
Conditional (Ternary) Operator
7. Bitwise Operators
Bitwise operators perform operations on the binary representations of numbers.
` ` Bitwise OR
^ Bitwise XOR echo $((5 ^ 3)) → 6
current_date=$(date)
echo "Today's date is $current_date"
Explanation: The output of the date command is stored in the variable current_date .
9. Brace Expansion
Brace expansion is used to generate a series of strings at once.
Example:
pwd
echo $? # this will print 0, as pwd command has no issue and will definetely
run
To get access of this status code, there is a variable which stores after any command is execute
i.e. $? .
Note:
echo commands mostly always have 0 exit code as they mostly get no errors
While loop
While loop takes a condition and keeps on running it's block of code till the time the condition
doesn't become false.\
while [ condition ]
do
# some logic
done
To write a while loop that prints counting of first 10 natural numbers we can do:
i=1 # this is a variable using which we will control the while loop to start
and stop
while [ $i -le 10 ]
do
echo "Value of i is: $i"
i=$((i+1)) # increment the value of i by 1
sleep 1 # pause the execution by 1 second
done
Here in the condition we are checking $i -le 10 i.e. till the time i is less than or equal to 10
we keep on executing the block of while , and the moment i goes beyond 10, we terminate the
loop and move ahead.
while [ -f conditionals.sh ]
do
echo "File exists till $(date +%H-%M-%S)"
sleep 5
done
Here we keep on running the while loop till the time the considionals.sh file exist in the same
folder as that of the loop file. For every iteration of the loop we sleep for 5s, which is kind of a
delay we are adding up.
Once you rename / remove the file the loop stops immediately when it checks the condition.
Problem Statement:
Create a Bash script that monitors a directory and continuously counts the number of files in it.
The script should print the file count every 10 seconds, and if the directory becomes empty, the
script should exit with a message indicating that the directory is empty.
Solution:
directory_to_check=$1
while [ "$(ls -A $directory_to_check)" ] # till the time directory exists
keep running the loop
do
file_count=$(ls -1 $directory_to_check | wc -l)
echo "File count in the direct is $file_count"
sleep 5
done
Command Breakdown
1. ls -1 ~/testdir :
ls : This is the list command in Unix/Linux, which is used to list the contents of a
directory.
-1 : This option tells ls to list each file or directory name on a separate line. Without
this option, ls might display files in a more compact, multi-column format.
~/testdir : This specifies the directory whose contents you want to list. ~
represents the home directory of the current user, so ~/testdir refers to the
testdir directory inside the user's home directory.
Example Output:
If ~/testdir contains three files named file1 , file2 , and file3 , the output of ls -1
~/testdir would be:
file1
file2
file3
2. | (Pipe Operator):
The pipe | is used to pass the output of one command as input to another
command. In this case, the output of ls -1 ~/testdir is passed to the next
command, wc -l .
3. wc -l :
wc stands for "word count," but it can also count lines and characters.
-l : This option tells wc to count the number of lines in the input it receives.
In this context, wc -l will count the number of lines produced by the ls -1 ~/testdir
command.
Example Output:
For the previous example where ls -1 ~/testdir outputted three lines ( file1 , file2 ,
file3 ), wc -l would return 3 , which is the number of lines.
for i in 1 2 3 4 5 6 7 8
do
echo "i is $i"
sleep 1
done
Here, we have a variable i, used inside the for loop which goes one by one to every single items
of list and take it's value. Then just like while loops we use do and done to create a block of
for which will be executed again and again till the time i takes value of each and every
single item.
Note:
In bash, if we want to represent a range of numbers we can use {x..y} . Here the range will
start from x and incrementally move up till y.
So, we can modify our for loop using this range syntax.
for i in {1..8}
do
echo "i is $i"
sleep 1
done
Interesting problem
Let's say we want to generate zip of every file inside a folder separately then we can use the
below script:
Here the for loop has a file variable that goes to each and every single file in the directory
mentioned and then use the tar command to zip them up.
Problem Statement:
Write a Bash script that iterates through a directory containing text files. For each text file, the
script should:
#!/bin/bash
We are iterating to all the files in the target folder and then passing that whole file in the wc
command with -l flag for counting lines. And then appending the line count in the same file.
Data streams
Any text that we see as an output or pass as an input is classified into different stream of data.
We can control how the stdout and stderr be handled in our linux machine.
If we want to stream out correct output i.e. stdout in a file by dumping it we can use > or 1>
ls -l 1> output.txt
# or
ls -l > output.txt
With both of the above codes, if our command runs properly then it will dump its log inside
output.txt. but if the commands fails, then the failure log or stderr will not be dumped in
output.txt.
Here, if the command runs properly then the logs will not be dumped in error.txt, but iof the
command fails all the error logs will be dumped.
If we don't care and we want to dump everything i.e. stdout and stderr both then we can use &>
ls -l &> log.txt
Now, doesn't matter stdout and stderr both will be dumped in log.txt
If we want to separately stream both stdout and stderr then we can combine the usage of 1>
and 2> .
Here all the stderr logs will be streamed to error.txt and stdout to success.txt
Now to handle taking input from user in bash, we can use stdin. To trigger taking an input we
have a command called as read
Here, we first print Give input , and then we will wait for the user to give an input, once the
user gives an put then the script resume and prints the second log with value of num.
Function in bash
If we have a piece of logic that we want encapsulate in one place and use it multiple times, then
we can wrap that logic into a function. Function is a piece of code, that has some logic, which
can take some input, process the input from that logic and generates an output.
function_name() {
# here we can write function logic
}
Example:
greet() {
echo "Hello world"
}
Here we have defined a function, by first giving it a name, then putting a pair of parenthesis,
and then a pair of curly braces to create a block of the function. Now in this block we can write
some logic.
Example 2:
To take a user input in a function call, we can pass the input space separated when calling the
function and inside the function we can use $1 for first incoming input, $2 for the second one
and so on.
square() {
v=$1
sq=$((v*v))
echo $sq
}
square 4
square() {
v=$1
sq=$((v*v))
echo $sq
}
r1=$(square 4)
echo "R1 is $r1"
Here whatever is the log result of square will be stored inside r1 . If we don't want to store an
echo log, and actually return something then we can use return keyword.
square() {
v=$1
sq=$((v*v))
return $sq
}
square 4
r1=$? # $? has the result return from square 4
echo "R1 is $r1"
With a return keyword, whatever we return from the function call, is stored inside $1. We can
fetch that response from their.
How we can schedule our scripts to run at a particular
time ?
We can automate when the system should run our script, we can decide if we want to run it
immediately, or 5 mins after the schedule, or daily at 6:00AM in the morning and what not.
To do this scheduling there are a lot of different ways but the most widely accepted is :
at package
This is a software that we can install in our linux shell, so that we can schedule jobs for future.