DEV Community

Cover image for Conquering the Terminal: An Introduction to Bash Scripting
Ibraheem101
Ibraheem101

Posted on

Conquering the Terminal: An Introduction to Bash Scripting

The first encounter I had with the command line was at a relative's house many years ago. I was captivated by the sight of my relative effortlessly typing what seemed like cryptic commands onto a black screen. To me, that was the epitome of what a programmer looked like. Eager to explore this mysterious world, I rushed home and opened up the command prompt. With a mix of excitement and curiosity, I began typing commands that I had memorized, such as cd and mkdir, even though I didn't understand their functions at the time. However, these once-unfamiliar commands began to reveal their purpose and significance as I learned more about programming. Now, equipped with a better understanding, I'm thrilled to introduce you to the fascinating world of bash scripting. In this brief tutorial, I'll assume that you're already familiar with basic Linux commands like echo, ls, cd, mkdir, and nano.

Bash scripting allows you to unlock the true potential of the command line.
Let's break things down. What does bash mean?
Bash stands for Bourne-Again Shell. It is a command line interpreter It executes commands read from a standard input or a file. It's the default shell for most Unix-like operating systems, including Linux and macOS.
Now what does scripting mean?
Scripting is the process of writing and executing scripts using the Bash shell. A script is a series of commands and instructions that are written in a scripting language (in this case, Bash) and executed sequentially.

Time to create your first script

  • Open any text editor. Create a file and name it anything with a .sh extension. For example, myscript.sh.
    I am using Nano text editor. To open a new file on nano, type nano <name of your file> in your command prompt on your Linux or Mac, or WSL if you are using a Windows computer.

  • The hashbang: The hashbang or the shebang is the first line of a bash script. It consists of a hash character (#) followed by an exclamation mark (!). The shebang is then followed by the path to the interpreter executable. This shebang line tells the system to use the Bash interpreter located at /bin/bash to execute the script.
    In a Bash script, it looks like this:

#!/bin/bash
Enter fullscreen mode Exit fullscreen mode

Note also that a comment in Bash begins with the (#) symbol

  • Basic "Hello World!": To display anything on the command line, we use the echo command.
#!/bin/bash

echo "Hello World!"
Enter fullscreen mode Exit fullscreen mode
  • Make the script executable: Before we can run this script, we have to grant it execute permission using the chmod command. Navigate to the directory where the script is stored and run the following: sudo chmod +x myscript.sh Run ls -l and we'll see that the color of the file name is now green. Also, rwxr indicates that the user has read, write and execute rights.

Executable and non-executable scripts

  • Run the Script: Navigate to the directory containing your script and execute it using its filename. In your case:

./myscript.sh

This is just a glimpse into the world of bash scripting. As you progress, you will explore variables, arithmetic operations, conditional statements, loops, and functions.

Variables

Just like in other programming languages, a variable is a container that stores some data. Variable names in Bash consist of alphanumeric characters and underscores (a-z, A-Z, 0-9, _). They cannot start with a digit. Conventionally, variable names are written in uppercase, but this is not mandatory.
To assign a value to a variable, we use the = operator with no spaces around it.

#!/bin/bash

myvar=23
name="Ayo"
Enter fullscreen mode Exit fullscreen mode

So, let's store "Hello World!" in a variable called myvar and display it on the screen.

#!/bin/bash

myvar="Hello World!"
echo $myvar
Enter fullscreen mode Exit fullscreen mode

What's with the dollar($) sign?
To access the value stored in a variable, we prefix the variable name with a dollar sign. If you don't do that in the example above, "myvar" will be displayed instead of "Hello World!".
Feel free to try different examples in your text editor.

Arithmetic Operations

Arithmetic operations are possible in almost all programming languages and Bash is no different. The table below shows some arithmetic operations and their symbols in Bash.

Operation Symbol
Addition +
Subtraction -
Multiplication *
Division /
Modulo (Remainder) %
Exponentiation **
Increment ++
Decrement --

To write a simple math expression, we use the expr command. For example, 10 + 3 will be written as:

expr 10 + 3

To store the result of an expression in a variable, we can use the double parenthesis: $((...)).

#!/bin/bash

result=$((10+3))
echo result
Enter fullscreen mode Exit fullscreen mode

The result of the expression 10+3 is stored in result and the result of 13 is displayed.

Arithmetic operations with variables are also possible. Storing your values in variables before performing computation helps improve your code's readability and flexibility. Try the following example:

#!/bin/bash

num1=100
num2=4
result=$(($num1 % $num2))
# or result=$(expr $num1 % $num2)
echo $result
Enter fullscreen mode Exit fullscreen mode

In the example above, we stored 100 and 4 in num1 and num2 respectively. We then performed the modulo operation which returns the remainder when num1 is divided by num2: 100 % 4 which will give 0.

So now try this example: 100 divided by 3. Your answer should be 33.333..

#!/bin/bash

num1=100
num2=3
result=$(($num1 / $num2))
echo $result
Enter fullscreen mode Exit fullscreen mode

Did you notice that the fraction part of the result was left out? This is because Bash primarily deals with integers. Bash stores numbers as integers and performs calculations with limited precision.
One solution to this is to use the bc command. Here's the same example you tried but using the bc command. You might need to install it if you get an error that bc command is not found. Install it using this command: sudo apt-get install bc

#!/bin/bash

num1=100
num2=3
result=$(echo "scale:3; $num1 / $num2" | bc)
echo $result
# 33.333 will be displayed
Enter fullscreen mode Exit fullscreen mode

The scale keyword tells the computer how many numbers you want after the decimal.

Bash Conditionals

Bash conditionals are fundamental constructs used for decision-making within Bash scripts. They allow you to execute different sets of commands based on whether certain conditions are true or false. The primary conditional statements in Bash are the if, elif (short for "else if"), else, and fi (which signifies the end of an if block) statements.

Before we see how these statements are used, take a look at the table below showing some important numeric comparison operators in Bash scripting. This will be useful later.

Name Operator
Greater Than -gt
Greater Than or Equal To -ge
Less Than -lt
Less Than or Equal To -le
Equal To -eq
Not Equal To -ne

Now open your text editor and write a script to read a number from a user and check if the number is even or odd.
Haha! We haven't learned how to read user input yet. In bash, we use the read keyword. To display a prompt, we use the read keyword with a -p flag. Here's how it works:

#!/bin/bash

# Read a number from the user and display it:
read num
echo $num

# Prompt the user to enter a number and display it:
read -p "Enter a number: " num
echo $num

Enter fullscreen mode Exit fullscreen mode

In both cases, the number is stored in the num variable

So, going back to your task, we read the number from the user and check if it's even or odd. Then, we print the corresponding message to the user:

#!/bin/bash

read -p "Enter first number: " num

if [ $(($num % 2)) -eq 0 ]; then
echo "Even Number"
else
echo "Odd Number"
fi
Enter fullscreen mode Exit fullscreen mode

Do you understand the code? If you don't, not to worry. Here is a detailed explanation:
read -p "Enter first number: " num: This prompts the user to enter a number and stores the input in the variable num.
if [ $(($num % 2)) -eq 0 ]; then: checks if the remainder of dividing the number by 2 is equal to 0, indicating that the number is even.
echo "Even Number": prints "Even Number" if the condition is true.
else: executes if the condition in the if statement is false.
echo "Odd Number": Prints "Odd Number" if the number is not even.

Loops

Just like other languages, loops allow you to execute a block of code multiple times.
Bash provides three primary loop statements; for, while and until. You'll explore the for and while loops.

The for loop:

The basic structure of a for loop is:

for item in ${list}; do
  # Commands to be executed for each item
done
Enter fullscreen mode Exit fullscreen mode

Write a for loop to display all the numbers from 1 to 5.

#!/bin/bash
for i in 1 2 3 4 5; do
    echo $i
done
Enter fullscreen mode Exit fullscreen mode

Another way to do it is by using the following format known as brace expansion:
for i in {start..end..step}; do or just for i in {start..end}; do if we want to display all the numbers from the start number to the end without any step.

#!/bin/bash

for i in {1..5}; do
    echo $i
done
Enter fullscreen mode Exit fullscreen mode

In either case, your output should be:

Output

To make your code more efficient, use an array to store the numbers and loop through the array.

#!/bin/bash

numbers=(1 2 3 4 5)

for i in ${numbers[@]}; do
echo $i
done
Enter fullscreen mode Exit fullscreen mode

Okay, what's the [@] used for?
numbers[@] is used to expand all the elements of the array and the for loop iterates through every element.

Write a Bash script to print out the ages of a person from 1 to 15, incrementing by 2 years for each age.
You can use the brace expansion method I explained previously:

for i in {1..15..2}; do
    echo "I am $i years old"
done
Enter fullscreen mode Exit fullscreen mode

Another way to do this is by using the double parentheses.
for ((initialization, condition, increment)); do

for ((i=1; i<=15; i+=2)); do
    echo "I am $i years old"
done
Enter fullscreen mode Exit fullscreen mode

for ((i=1; i<=15; i+=2)) has the following features:

  • Initialization: i=1 sets the initial value of the counter variable i to 1.
  • Condition: i<=15 checks if i is less than or equal to 15. The loop continues as long as this condition is true.
  • Increment: i+=2 increments the value of i by 2 in each iteration.

The while loop

While we've conquered iterating through predefined lists using the for loop 😂, Bash scripting offers another powerful tool for repetitive tasks: the while loop. Unlike the for loop, which thrives on a set list of elements, the while loop keeps executing a block of code as long as a specific condition remains true.

The format for a while loop is shown below:

while condition; do
    # Commands to be executed while the condition is true
done
Enter fullscreen mode Exit fullscreen mode

Write a while loop to count down from 10 to 5.

  • First define a counter (where you want to begin counting from)
  • We want to decrement this counter by one during every iteration. So tell the computer that as long as counter is greater than or equal to 5, display its value and then decrement by one.
counter=10
while [ $counter -ge 5 ]; do
    echo "Countdown: $counter"
    ((counter--))
done
Enter fullscreen mode Exit fullscreen mode

If you got it right, this should be your output:

Output

Do you have what it takes for the next task?
Write a script to retrieve input from a user until the user enters the string: exit.
Here's what to do.

  1. Create an empty variable that will store the user input.
  2. Write a while loop with a condition that the string value in the variable is not equal to "exit".
  3. do: read the input from the user.
  4. done
user_input=""
while [ "$user_input" != "exit" ]; do
    read -p "Enter 'exit' to quit: " user_input
done

echo "Exit Successful"
Enter fullscreen mode Exit fullscreen mode

So this is what the code does:
user_input="": initializes the variable user_input to an empty string.
while [ "$user_input" != "exit" ]; do: starts a while loop that continues as long as the value of user_input is not "exit".
read -p "Enter 'exit' to quit: " user_input: prompts the user to enter a value and stores it in the variable user_input.
echo "Exit Successful": Prints "Exit Successful" after the while loop exits, indicating that the user has entered "exit".

Functions

Functions in programming allow you to encapsulate a sequence of commands into a reusable block of code. They improve code readability, maintainability, and reusability by breaking down complex scripts into smaller, more manageable pieces.
As Martin Fowler aptly stated,

"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."

You declare a function in Bash using the function keyword followed by the function name and parentheses.

function my_function() {
    # Function commands go here
}
Enter fullscreen mode Exit fullscreen mode

Omitting the function keyword also works

my_function() {
    # Function commands go here
}
Enter fullscreen mode Exit fullscreen mode

To invoke or call a function, you simply use its name

my_function() {
    # Function commands go here
}

# function call
my_function
Enter fullscreen mode Exit fullscreen mode

Functions can have arguments. You'll learn a way of passing arguments to bash scripts and functions from the following example.

greet() {
    echo "Hello $1, nice to meet you!"
}

greet "Dan"

Enter fullscreen mode Exit fullscreen mode

What do we have here?
In this example, the greet() function takes one argument, $1, which represents the first argument passed to the function. $2 represents the second, $3 represents the third, and so on.
When the function is called with greet "Dan", the string "Dan" is passed as an argument to the function. Inside the function, $1 is replaced with the value "Dan".

So if you wanted to write a function to pass your name and age as parameters and display them in a string, your function would look like this:

myself() {
    echo "Hello I am $1, I am $2 years old"
}

myself "Fawz" 26
Enter fullscreen mode Exit fullscreen mode

$1 and $2 are replaced by Fawz and 26 respectively.

The return statement

Bash functions do not have explicit return types, but they can return a value using the return statement. The return value is stored in the special variable $?.

Write a function to add two numbers. Use the return statement to return the result. Remember that you will need to use echo to display the result stored in the $? variable.

function add() {
    result=$(( $1 + $2 ))  # Calculate the sum
    return $result  # Return the result
}

# Call the add function
add 5 3

echo "The sum is: $?"
# This will display "The sum is: 8"
Enter fullscreen mode Exit fullscreen mode

Conclusion and Further Exploration

Congratulations! You've successfully conquered the basics of Bash scripting. You've learned how to navigate the command line, write scripts to automate tasks, and control the flow of your programs using variables, conditionals, and loops. This newfound power opens doors to a vast world of possibilities, from automating repetitive tasks to managing complex system configurations.

But this is just the beginning of your Bash scripting adventure! You can check out the following resources to fuel your exploration:

Feel free to experiment, explore different scripts, and don't be afraid to make mistakes. The programming community is vast and welcoming, so don't hesitate to seek help online if you get stuck.

Like and Comment!

Did you enjoy conquering the terminal with this tutorial? If you found this introduction to Bash scripting helpful, or if you have any questions or suggestions, leave a comment below! I'd love to hear your thoughts and experiences.
Thank you for reading!

Top comments (3)

Collapse
 
ccoveille profile image
Christophe Colombier

Pretty complete. I would have suggested working with shellcheck and shfmt

GitHub logo koalaman / shellcheck

ShellCheck, a static analysis tool for shell scripts

Build Status

ShellCheck - A shell script static analysis tool

ShellCheck is a GPLv3 tool that gives warnings and suggestions for bash/sh shell scripts:

Screenshot of a terminal showing problematic shell script lines highlighted

The goals of ShellCheck are

  • To point out and clarify typical beginner's syntax issues that cause a shell to give cryptic error messages.

  • To point out and clarify typical intermediate level semantic problems that cause a shell to behave strangely and counter-intuitively.

  • To point out subtle caveats, corner cases and pitfalls that may cause an advanced user's otherwise working script to fail under future circumstances.

See the gallery of bad code for examples of what ShellCheck can help you identify!

Table of Contents

GitHub logo mvdan / sh

A shell parser, formatter, and interpreter with bash support; includes shfmt

sh

Go Reference

A shell parser, formatter, and interpreter. Supports POSIX Shell, Bash, and mksh. Requires Go 1.21 or later.

Quick start

To parse shell scripts, inspect them, and print them out, see the syntax examples.

For high-level operations like performing shell expansions on strings, see the shell examples.

shfmt

go install mvdan.cc/sh/v3/cmd/shfmt@latest

shfmt formats shell programs. See canonical.sh for a quick look at its default style. For example:

shfmt -l -w script.sh

For more information, see its manpage, which can be viewed directly as Markdown or rendered with scdoc.

Packages are available on Alpine, Arch, Debian, Docker, Fedora, FreeBSD, Homebrew, MacPorts, NixOS, OpenSUSE, Scoop, Snapcraft, Void and webi.

gosh

go install mvdan.cc/sh/v3/cmd/gosh@latest

Proof of concept shell that uses interp. Note that it's not meant to replace a POSIX shell at…

Collapse
 
ibraheem101 profile image
Ibraheem101

Thank you so much for suggesting shellcheck and shfmt! Do you have any other tools you'd recommend for bash scripting?

Collapse
 
ccoveille profile image
Christophe Colombier

Patience? 🤣😅

More seriously, shfmt and shellcheck are amazing.

You can find plugins for IDEike this one

github.com/vscode-shellcheck/vscod...

You could also use precommit hook, or github actions to run them when you commit or push