Bash Scripts Quickstart Guide

in bash, linux

Bash scripts are a great way to automate simple tasks in our electronic day-to-day life. Once you get used to writing them, it easily becomes a productivity increasing habit. Here's a list of my most often used bash features and constructs.

Shebang and Options

I like to start every bash script with the following 3 lines:

#!/usr/bin/env bash
set -e
set -o noclobber

The first is the shebang line which lets us run the script as executable. Note the use of env which makes the script more portable, as we're no longer bound to where bash is installed.

The second line enters "error sensitive" mode, which will break out of the script in case of unexpected errors. This is helpful in preventing error snowball effect, where the failure of one command leads to more failures down the road.

The third line enters noclobber mode which protects accidental file clobbering (writing over a file by redirecting with the > symbol). You can still clobber a file intentionally using >|.

Bash Variables

Variables are defined using a single equal sign with no spaces around it. To its left is the variable name and to the right is the value. Variable names are written in lowercase by convention and multiple words separated by underscores. Some examples:

target_location=/usr/local
file_name=demo.mp3
full_path="$HOME/$FILE_NAME"
opts="-R"

Variables can include lists of files. Create list by wrapping the value in parenthesis (without quotes):

files=($HOME/*.txt)

System environment variables are written in uppercase. Some useful ones include:

  1. HOME - user's home directory
  2. USER - current user
  3. CWD - current directory

Reading the value of a variable is done by prefixing a dollar sign to its name. To disambiguate we can use curlies when needed. Here are some examples:

echo "$file_name"

# Array length
echo "You have ${#files[*]} files"

# Array item by index
echo "The first is: ${files[0]}"
echo "The second is: ${files[1]}"

Bash also supports derived values from variables when reading a value, which lets us cut a prefix or suffix from it:

file=/home/ynon/demo.mp3
echo "$file Is /home/ynon/demo.mp3"

# cut prefix until the first dot
echo "${file#*.} is mp3"

# cut suffix until the first dot
echo "${file%.*} is /home/ynon/demo"

The special variable $0 contains the script name and the positional variables $1, $2, $3, etc. contain the arguments passed to the script. For example the following snippet calculates the path of the currently running script:

script_path="`realpath ${0%/*}`"

Conditions

The command if defines a condition. Bash supports two types of conditional expressions: if command and if expression.

The first executes a command and branches according to its exit status. For example:

# if command
site=www.google.com

if httping -c 1 "$site" >& /dev/null
then
    echo "Website $site Responded. It's probably alive"
fi

The above code executes httping -c 1 www.google.com, redirects the output to /dev/null and prints a message if the command succeeded.

A second version is if expression which evaluates a boolean expression and branches according to its value:

if [[ -f /etc/passwd ]]
then
    echo "path /etc/passwd is a file"
fi

Bash has many built-in test operators. The above code uses -f which checks if a path is a file. Here are some more:

  1. -f path is a regular file
  2. -d path is a directory
  3. -e path exists
  4. -r, -w, -x file at path is readable, writable or executable.
  5. -s file is not empty
  6. -z string string is zero length

Loops

Bash supports 2 types of for loops and also while loops. Let's start with the for loops iterating over a list:

# for each text file in my home directory
files=/home/ynon/*.txt
for f in files
do
    echo "$f"
    head -1 "$f"
    echo
done

Classic for loops are supported and written using double parenthesis:

# count from 2 till 20
for (( i=0; i <= 20; i++ ))
do
    echo $i
done

Finally while loops work as you'd expect from other languages. Like if, they can also be used either with commands or with boolean expressions.

The following snippet searches for an available file name with a known prefix using while:

i=0
while [[ -f myfile_$i ]]
do
    (( i++ ))
done

echo "Found new name: myfile_$i"
touch myfile_$i

Functions

Bash supports functions using the keyword function. Inside a function's body the variables $1, $2, $3, etc. hold the arguments passed to the function. The following function mcd will call mkdir and then cd to it:

function mcd {
    mkdir -p "$1"
    cd "$1"
}

# creates a new directory and cd to it
mcd demo

Functions can declare local variables using the local keyword. They can only return integer values in $? variable. For strings results we can echo the value in the function and read it outside:

function find_file_name {
  local i=0
  while [[ -f myfile_$i ]]
  do
    (( i++ ))
  done

  echo "myfile_$i"
}

fname=`find_file_name`
echo "Found: $fname"

touch "$fname"

Further Reading & Resources

The website https://www.tutorialspoint.com/execute_bash_online.php provides a live bash repl so you can type in short scripts and see their results in the browser.

A full bash reference is available with man bash or online at:
https://tiswww.case.edu/php/chet/bash/bashref.html

Comments