Bash: the Born Again SHell

Introduction*

Common Utilities/Tools/Commands*

Bash Scripting

Basic Bash Scripting

Each script starts with a shebang and the path to the shell that you want the script to use, like so:

#!/bin/bash

The #! combo is called a shebang by most Unix geeks. This is used by the shell to decide which interpreter to run the rest of the script, and ignored by the shell that actually runs the script. Confused? Scripts can be written for all kinds of interpreters — bash, tsch, zsh, or other shells, or for Perl, Python, and so on. You could even omit that line if you wanted to run the script by sourcing it at the shell, but let's save ourselves some trouble and add it to allow scripts to be run non-interactively.

What's next? You might want to include a comment or two about what the script is for. Preface comments with the hash (#) character:

#!/bin/bash
# A simple script

Let's say you want to run an WHATEVER command from the script, rather than typing it each time. Just add the WHATEVER command to the script that you want to use:

#!/bin/bash
# WHATEVER script
WHATEVER -abc /home/user/Documents/FILE

Save your file, and then make sure that it's set executable. You can do this using the chmod utility, which changes a file's mode. To set it so that a script is executable by you and not the rest of the users on a system, use chmod 700 scriptname — this will let you read, write, and execute (run) the script — but only your user. To see the results, run ls -lh SCRIPTNAME and you'll see something like this:

-rwx------ 1 yourself yourself   21 2010-02-01 03:08 echo

The first column of rights, rwx, shows that the owner of the file (yourself) has read, write, and execute permissions. The other columns with a dash show that other users have no rights for that file at all.

Variables

The above script is useful, but it has hard-coded paths. That might not be a problem, but if you want to write longer scripts that reference paths often, you probably want to utilize variables. Here's a quick sample:

#!/bin/bash
# WHATEVER using variables

SOURCEDIR=/home/user/Documents/

WHATEVER -abc $SOURCEDIR/aFile.txt

There's not a lot of benefit if you only reference the directories once, but if they're used multiple times, it's much easier to change them in one location than changing them throughout a script.

Taking Input

Non-interactive scripts are useful, but what if you need to give the script new information each time it's run? For instance, what if you want to write a script to modify a file? One thing you can do is take an argument from the command line. So, for instance, when you run script func, the script will take the name of the first argument (foo):

#!/bin/bash
echo $1

Here bash will read the command line and echo (print) the first argument — that is, the first string after the command itself.

You can also use read to accept user input. Let's say you want to prompt a user for input:

#!/bin/bash
echo -e "Please enter your name: "
read name
echo "Nice to meet you, $name"

That script will wait for the user to type in their name (or any other input, for that matter) and use it as the variable $name. Pretty simple, yeah?

A More Advanced Example

The following script reads the user's name, declares an array of strings, stores its length, generates a random indez and last invites the user to participate in some activity.

#!/bin/bash

# Greet user and request their name
echo "The activity generator"
read -p "What is your name? " name

# Create an array of activities
activity[0]="Football"
activity[1]="Table Tennis"
activity[2]="8 Ball Pool"
activity[3]="PS5"
activity[4]="Blackjack"

array_length=${#activity[@]} # Store the length of the array
index=$(($RANDOM % $array_length)) # Randomly select an index from 0 to array_length

# Invite the user to join you to participate in an activity
echo "Hi" $name, "would you like to play" ${activity[$index]}"?"
read -p "Answer: " answer

How to Create a Function in a Bash Script

In programming, functions are used to bundle a set of repeated instructions together. The purpose of writing functions is to avoid writing the same code constantly, and it also makes your code more readable.

A bash function may be considered a typical function: a set of commands that can be called several times. However, bash functions are slightly more limited when compared to the majority of programming languages (i.e., bash functions do not allow you to return a value when called).

Before we create a bash script using a function, let's take a look at the syntax for declaring a bash function. We can define a function by writing a function name, followed by parentheses and curly braces for the function's body – this is the most common way to write a bash function. Here is it looks in code:

# Most common way to write a bash function.
example_function () {
  commands
}

The other way to format a bash function is to begin the line with the reserved bash keyword function, then the function name and curly braces for the function body – we do not need the parentheses.

# Another way to write a bash function.
function example_function {
  commands
}

Passing arguments to bash functions

Arguments are variables used in a specific function. In bash, we can not pass arguments to functions like you would in high-level programming languages such as Python, Julia, etc. Instead, bash functions have their own command line argument and we can pass parameters to a function by placing them after a call to the function name – separated by a space if there are multiple arguments.

In essence, function arguments are treated as positional parameters and shell variables ($1, $2, ..., $n) are used to access arguments passed to the function.

#!/bin/bash
greet () {
echo "Hello $1 $2"
}

greet "John" "Doe"

In the bash script above, we passed two arguments, John and Doe, to our greet function. When the bash script is run now, we should see the following output:

Hello John Doe

Writing If Statements in Bash

If statements are conditional statements used in programming languages. If a statement is proven to be true, then a condition would run. This functionality enables us to determine the circumstances under which a certain piece of code shall be executed.

In bash, we start the conditional with an if statement and end it with fi (if spelled backward). Here is an example bash script with if conditionals:

#!/bin/bash

read -p "Give me a number: " number
if [ $number -gt 10 ];
then
  echo "Your number is greater than 10."
fi

In the bash script above, the user is asked to provide a number. If the number provided by the user is greater than 10, then Your number is greater than 10 will be outputted to the terminal, but if it is not, the program will end.

Here is another example:

#!/bin/bash
salary=1000
expenses=800
#Check if salary and expenses are equal
if [ $salary == $expenses ];
  then
    echo "Salary and expenses are equal"
#Check if salary and expenses are not equal
  elif [ $salary != $expenses ];
    then
      echo "Salary and expenses are not equal"
fi

Loops

A loop is an essential tool in various programming languages. To put it simply, a bash loop is a set of instructions that are repeated until a user-specified condition is reached. Start by creating a loop bash program (whileloop.sh):

#!/bin/bash
n=0
while :
do
  echo "Countdown: $n"
  ((n++))
done

This will work as a countdown to infinity until you press CTRL + C to stop the script.

Now that we've tested the while loop, we can move on to the for loop. Create a bash file (forloop.sh) for it:

#!/bin/bash
for (( n=2; n<=10; n++ ))
  do
  echo "$n seconds"
  done

The script prints out numbers from 2 to 10 while adding the seconds keyword to it.

Creating an Array

A bash array is a data structure designed to store information in an indexed way. It is extra useful if users need to store and retrieve thousands of pieces of data fast. What makes bash arrays special is that unlike any other programming language, they can store different types of elements. For example, you can use a bash array to store both strings and numbers.

First, create a new file (array.sh) in the current directory:

#!/bin/bash
# Create an indexed array
IndexedArray=(egg burger milk)
#Iterate over the array to get all the values
for i in "${IndexedArray[@]}"; do echo "$i"; done

This script iterates over the IndexedArray and prints out all the values.

Strings

We can calculate the length of a string with the # operator, as in:

#!/bin/bash
# Create a new string
mystring="lets count the length of this string"
i=${#mystring}
echo "Length: $i"

If we need to remove unnecessary parts from strings, we can use the Bash string extraction facilities. The following script has 4 values, 3 of them being strings. In our example, we will extract only the number value. This can be done via the cut command. First, we instruct the command that each variable is separated by a comma by using the -d flag. Then we ask the cut command to extract the 5th value.

#!/bin/bash cut -d , -f 5 <<< "Website,Domain,DNS,SMTP,5005"

In another example, we have a string that is mixed with some numbers. We will use expr substr commands to extract only the randomized text value.

#!/bin/bash expr substr "458449randomized4132" 7 9

Another common bash operation is to find and replace, which doesn't require any special commands. It can all be done with string manipulation.

#!/bin/bash first="I drive a BMW and Volvo" second="Audi" echo "${first/BMW/"$second"}"

Concatenaing strings can be as simple as:

#!/bin/bash firststring="The secret is..." secondstring="Bash" thirdstring="$firststring$secondstring" echo "$thirdstring"

The above script will connect the values of firststring and secondstring variables creating a whole new thirdstring.

A more advanced example would look like this:

#!/bin/bash firststring="The secret is..." firststring+="Bash" echo "$firststring"

Intermediate Bash Scripting

Generating the Factorial of Number

The factorial of a number is the result of all positive descending integers. For example, the factorial of 5 would be 120:

5! = 5*4*3*2*1 = 120

Factorial scrips are very useful for users learning about recursion! Look at the following example:

#!/bin/bash echo Enter the number you want to get factorial for read mynumber factorial=1 for ((i=1;i<=mynumber;i++)) do factorial=$(($factorial*$i)) done echo $factorial

The preceding script will ask the user to enter a number they want to get the factorial of and use a for loop to calculate it.

Creating Directories

It is effortless to create directories in bash unless you need to create a lot of directories quickly. In the following example, we will use the bash script to create a set of directories with the same subdirectories in each. The following script does just that. Note that this line of code will achieve the same on the command line.

#!/bin/bash mkdir -p {Painting,Science,Electronics}/{bibliography,textbooks,images}

Reading Files

We will be using a sample file (mysamplefile.txt) to read from. Then you can load a variable with it in one line:

#!/bin/bash myvalue=`cat mysamplefile.txt` echo "$myvalue"
Note how the subcommand cat mysamplefile.txt has been enclosed in back ticks.

Reading Lines One-by-one

We'll print a file (apoem.txt) with its line count. The file is a poem, so each line is a verse. In the following example, each iteration of read stores a line in variable verse. Then a line number, a colon and the line are printed.

#!/bin/bash myfile='apoem.txt' i=1 while read verse; do echo "$i : $verse" i=$((i+1)) done < $myfile

Note how counter i is incremented (i=$((i+1))) and the input is fed in at the back through < $myfile

Testing if a File Exists*

Use an if statement with a -f flag. The flag checks if a given file exists and is a regular file.

#!/bin/bash myfile='apoem.txt' if [ -f "$myfile" ]; then echo "$myfile exists." else echo "$myfile does not exist." fi