2510 words
13 minutes
Bash Shell Scripting
2021-11-08
2022-11-08

Shell Scripting is a way to automate the execution of commands that we frequently use. For example, if we want to create a new file and fill it with some lines of text, we can do this manually by typing the commands, or we can create a script that contains these commands and run it.

In this post, we will discuss Shell Scripting using Bash Shell, and it will serve as a primary reference for more in-depth discussions on creating shell scripts.

Check out the previous post about Bash Shell

Bash provides a set of commands that can be used to create small programs, which we conventionally call scripts.

Bash scripts can be a single command or a series of commands.

Notice the difference, we do not call this Bash Programming but Bash Scripting, and this is a very important distinction. We do not call Bash Scripting “Bash Programming” because you can achieve something very complex before feeling that the script is out of control.

But Bash Scripting is great because you don’t need to do anything else to run the program, yes because Bash Scripting does not need a compiler and interpreter, just the shell.

There are many things that will be missed from programming languages like Perl, JavaScript, or Python.

Variables do not have a specific scope, they are global, there is no standard library, you do not have a module system, for example. But a big advantage: you can easily call CLI tools as if you were in the shell, and the Unix approach has many small utility commands that make shell scripting shine. Making network requests with wget, processing text with sed, and manipulating files with awk and grep, and much more.

Shell Scripting is one of the very useful tools you should know, at least to create simple scripts, be able to read code when you see it, and it can be used in daily work.

This post provides a theoretical and conceptual guide to Bash Scripting, I will post more specific ways or problem-solving methods in the future.

Basics#

Bash scripts are stored in files. You can name them anything with the .sh or .bash extension. It can also be a text file without an extension. The most important thing is that it must start with a “shebang” on the first line.

#!/bin/bash

and it must be an executable file.

A file is set as executable using chmod, one of the utility commands.

You can use it like this:

chmod u+x thisscript

to make thisscript an executable file for your user (but I don’t want to discuss file permissions here, and will discuss it in another post later).

Now, you can execute the file, if you are in the same folder you can call it with ./thisscript, or use the full path, for example, if the script file is in /home/memet/Scripts then I, who am in the /home/memet/ folder, can call it with ./Scripts/thisscript.

While learning, you can use online tools like https://rextester.com/l/bash_online_compiler to test your bash scripts, for easier use.

Comments#

Comments are lines that will not be executed by the shell and are usually used to provide explanations or notes. They are one of the important things in writing a program.

A line that starts with # will be considered a comment and will not be executed by the shell.

#!/bin/bash
# this is a comment

Comments can also start at the end of a line, after a command, and will not be executed by the shell.

#!/bin/bash
echo "Hello World" # this is also a comment

Variables#

Variables are a place to store values and can be accessed by the variable name. You can create a variable using the = sign.

name=value

The example above creates a variable named name and its value is value.

Other examples:

Name=Memet
ROOM=6969
nickname="zxce3"

You can use variables by adding $ in front of the variable name.

echo $Name
echo $ROOM
echo $nickname

You can also use variables without $ if you use double quotes ".

echo "$Name"
echo "$ROOM"
echo "$nickname"

Operators#

Operators are symbols used to perform mathematical, comparison, logical, and other operations.

Bash implements the same operators as other programming languages:

  • + for addition
  • - for subtraction
  • * for multiplication
  • / for division
  • % for modulus (remainder)
  • ** for exponentiation

You can compare values using operators:

  • == for equal to
  • < for less than
  • > for greater than
  • <= for less than or equal to
  • >= for greater than or equal to

You can use logical operators to compare:

  • -lt for less than
  • -gt for greater than
  • -le for less than or equal to
  • -ge for greater than or equal to
  • -eq for equal to
  • -ne for not equal to

Example:

#!/bin/bash
age=20
minimum=18
if [ $age -ge $minimum ]; then
    echo "you may enter"
else
    echo "you are not old enough"
fi

Logical operators:

  • && for and
  • || for or

Example:

#!/bin/bash
age=20
minimum=18
if [ $age -ge $minimum ] && [ $age -lt 30 ]; then
    echo "you may enter"
else
    echo "you are not old enough"
fi

These shortcuts can also perform arithmetic and comparison operations:

  • += for addition
  • -= for subtraction
  • *= for multiplication
  • /= for division
  • %= for modulus (remainder)

Example:

#!/bin/bash
age=20
age+=1
echo $age

There are actually many more operators, but those are the most commonly used.

Printing to the Screen#

Bash has several ways to print to the screen, namely:

  • echo to print text to the screen
  • printf to print text to the screen with a specific format

Example:

#!/bin/bash
echo "Hello World"
printf "Hello World"

Logical Conditions#

AND: evaluates to 0 (zero) if both command and othercommand are executed and produce a 0 (zero) value. If the result is zero when executed, the command is successful. Error messages are identified by non-zero values.

command && othercommand

OR: evaluates to 0 (zero) if at least one of command and othercommand is executed and produces a 0 (zero) value.

command || othercommand

NOT: reverses the logical value of the executed command.

!command

Example:

#!/bin/bash
if [ 1 -eq 1 ] && [ 2 -eq 2 ]
then
    echo "1 = 1 and 2 = 2"
fi
# output: 1 = 1 and 2 = 2

if [ 1 -eq 1 ] || [ 2 -eq 3 ]
then
    echo "1 = 1 or 2 = 3"
fi
# output: 1 = 1 and 2 = 3

if [ ! 1 -eq 2 ]
then
    echo "1 is not equal to 2"
fi
# output: 1 is not equal to 2

Control Structures#

Here you can also use control structures that you may already be familiar with.

if/else statement#

if/else statement is a control structure used to execute commands if a condition is met.

In Indonesian, if/else statement can be translated as jika/lain/jika tidak statement.

Simple if:

if condition
then
    command
fi

if with else:

if condition
then
    command
else
    othercommand
fi

if with elif:

elif is short for else if.

if condition
then
    command
elif othercondition
then
    othercommand
fi

if with elif and else:

if condition
then
    command
elif othercondition
then
    othercommand
else
    anothercommand
fi

Between if - then - else:

if condition
then
    command
elif
    othercommand
else
    anothercommand
fi

You can put else on the same line with a semicolon ;.

if condition; then
    command
else
    othercommand
fi

Example:

#!/bin/bash
WAIFU="Miku"
if [ "$WAIFU" == "" ]; then
    echo "Your WAIFU is empty"
else
    echo "Your WAIFU is $WAIFU"
fi
# output: Your WAIFU is Miku

Notice the parentheses? We need to add parentheses when we need to evaluate any kind of boolean expression.

loop#

While#

As long as the condition is true, the command will continue to run.

while condition
do
    command
done

Example:

#!/bin/bash
i=0
while [ $i -lt 5 ]
do
    echo $i
    i=$((i+1))
done
# output: 0 1 2 3 4

Until#

Until the condition is true, the command will continue to run.

until condition
do
    command
done

Example:

#!/bin/bash
i=0
until [ $i -eq 5 ]
do
    echo $i
    i=$((i+1))
done
# output: 0 1 2 3 4

For in#

Iterate over a list and execute the command for each item.

for item in list
do
    command
done

break and continue#

Inside a loop, you can use break and continue to stop or continue the iteration.

  • break is used to stop the iteration.
  • continue is used to continue the iteration.

Example:

#!/bin/bash
for i in {1..10}
do
    if [ $i -eq 5 ]
    then
        break
    fi
    echo $i
done
# output: 1 2 3 4

for i in {1..10}
do
    if [ $i -eq 5 ]
    then
        continue
    fi
    echo $i
done
# output: 1 2 3 4 6 7 8 9 10

case#

case statement is used to select one of many blocks of commands to execute.

case variable in
    pattern1)
        command1
        ;;
    pattern2)
        command2
        ;;
    *)
        othercommand
        ;;
esac

Example:

#!/bin/bash
WAIFU="Miku"
case $WAIFU in
    "Miku")
        echo "Miku is the best waifu"
        ;;
    "Rin")
        echo "Rin is the best waifu"
        ;;
    *)
        echo "Your waifu is not registered"
        ;;
esac
# output: Miku is the best waifu

Like fi in the if statement, esac is short for end case. And if you notice, esac is the same word as case but reversed.

We add a semicolon ; at the end of each command.

case variable in
    pattern1)
        command1;
        ;;
    pattern2)
        command2;
        ;;
    *)
        othercommand;
        ;;
esac

* will handle all conditions that are not listed. Use the | symbol to add other conditions.

read -p "Enter waifu name: " WAIFU
case $WAIFU in
    "Miku" | "miku")
        echo "Miku is the best waifu"
        ;;
    "Rin" | "rin")
        echo "Rin is the best waifu"
        ;;
    *)
        echo "Your waifu is not registered"
        ;;
esac

Select#

select is a statement used to create an interactive menu.

select variable in list
do
    command
done

Example:

#!/bin/bash
select REGION in "West Java" "Central Java" "East Java"
do
    if [ $REGION == "West Java" ]
    then
        echo "West Java is the best region"
    elif [ $REGION == "Central Java" ]
    then
        echo "Central Java is the best region"
    elif [ $REGION == "East Java" ]
    then
        echo "East Java is the best region"
    else
        break
    fi

Condition Testing#

I used the term condition above for control structures.

You can use test or [ to perform testing and check conditions and return a value of 0 if the condition is true and (not 0) if the condition is false.

Example:

#!/bin/bash
if test "Miku" == "Miku"
then
    echo "Miku is the best waifu"
fi

if ! test "Miku" == "Rin"
then
    echo "Miku is not Rin"
fi

Reading Input#

You can make your script interactive by using read to read input from the user. This command will read a line from standard input, and it can format the input in a very flexible way.

The simplest example:

echo "Room: "
read ROOM
echo "Hello $ROOM"

This will display Room: and wait for input from the user. After the user presses the enter key, the input will be stored in the ROOM variable and will display Hello $ROOM.

You can use -p to display a message before reading the input.

read -p "Enter waifu name: " WAIFU
echo "Your waifu is $WAIFU"

You can use -s to hide the input.

read -s -p "Enter password: " PASSWORD
echo "Your password is $PASSWORD"

There are many other options that can be used with read to read input, you can see help read to see all available options.

Adding Options#

Options are arguments that start with a - or -- and are usually used to change the behavior of the script.

Example:

ls -l
drink -t "coffee"

In your code, you can use getopts to handle options and parse the given arguments to get the value of the options.

If we accept options l and t then we feed getopts lt to the while loop.

If the t option accepts a value then we need to add : after t, and we format our getopts, and we will call it like this: getopts "l:t:" arg.

Example snippet:

#!/bin/bash
while getopts cd: arg
do
    case $arg in
        c) echo "Option $arg selected";;
        d) echo "Option $arg selected with value $OPTARG";;
    esac
done

In this script, $OPTARG automatically assigns the option letter. So we tell getopts what options we accept, and handle each case separately.

Errors are automatically handled by getopts. If you provide an unrecognized option, it will display an error message and exit the script. And we can choose how the error will be displayed, by using : before the argument definition: getopts :cd: arg.

We can also handle errors using \? and :. The first one is called when an unrecognized option is provided, and the second one when the provided option does not have a value.

#!/bin/bash
while getopts :cd: arg
do
    case $arg in
        c) echo "Option $arg selected";;
        d) echo "Option $arg selected with value $OPTARG";;
        :) echo "$0: Option $arg requires a value for -$OPTARG." >&2
        exit 1 ;;
        \?) echo "$0: Option -$OPTARG not recognized." >&2
    esac
done

Usage:

$ ./script.sh -c -d "Miku"
Option c selected
Option d selected with value Miku

$ ./script.sh -c -d
./script.sh: Option d requires a value for -d.

$ ./script.sh -c -e
./script.sh: Option -e not recognized.

So if you forget to add an argument, the error message will be different.

Working with Parameters#

In your script, you can access any parameters passed at execution time, you can access them using $0, $1, $2, and so on, depending on their position, where $0 is the command name and by adding a number, the parameter position will increase. After position 9 you need curly braces to access the parameter: 10,{10},{11}, and so on.

For example, a script for startMotor:

#!/bin/bash
echo $0
echo $1

When we run it, it will display:

$ ./startMotor
vario

The special syntax character $* will display all the provided parameters, and $# will display the number of provided parameters.

#!/bin/bash
echo $# $*
$ ./startMotor vario vixion beat
3 vario vixion beat

Splitting Options from Parameters#

If you have both options and parameters, you need to separate them with -- or - to separate options and parameters.

startMotor -m "vario" - vixion beat

Working with Strings#

Strings are sequences of characters enclosed in quotes.

Given a string:

MOTOR="vespa"

You can get its length with ${#MOTOR} and extract specific characters with ${MOTOR:0:1}.

Always use quotes to avoid issues when bash interprets special characters.

You can also compare two strings using == and =. == will return true if both strings are the same, and = will return true if both strings are the same and have the same length.

"$vario" == "$vario"
"$vario" = "$vixion"

This cannot be called because there are two spaces between =.

You can also use != to compare two strings that are not equal.

"$vario" != "$vixion"

Arrays#

Arrays are collections of values stored in a single variable. You can access them using an index, starting from 0. You can define an array using square brackets and separate each value with a space.

motor=("vario" "vixion" "beat")

You can reference any item in the array using brackets:

motor[0]
motor[1]

And you can get the length of the array using ${#motor[@]}.

Built-in Commands#

So far, we have used some built-in commands, such as echo, read, getopts, and exit. You can see the complete list using the help or man command.

Functions#

Just like JavaScript or other programming languages, you can create functions in bash. Functions in bash are the same as functions in JavaScript, you can pass parameters and return values.

You can create simple reusable code snippets, give them a name, and call them anytime.

Define a function with

function functionName() {
    # code
}

or like this

functionName() {
    # code
}

And to call it, simply use

functionName

You can pass parameters to the function without defining them - you just reference the parameters with $1, $2, and so on. Just like we did earlier with script parameters.

function cleanUp {
    echo "deleting $1"
}
cleanUp "/var/log"

Yes, it’s quite messy, but those are the basics of bash scripting.

Check out the snippets on this website as well. And you can request the snippet you need on Github.

References#

Bash Shell Scripting
https://zxce3.net/posts/shells/bash-shell-scripting/
Author
Memet Zx
Published at
2021-11-08