Bash Essentials To Create Useful Automation Scripts
8 min read

Why Bash

Bash is actually one of those languages I am most glad I learned. In my daily work, I tend to write a lot of Bash automations to ease some of my development processes. Even though the syntax might be a bit hard to understand at first, most scripts are not that difficult once you get the basics. And the best part is that Bash scripts can run on basically any environment.

This guide will begin with some very basics, and continue with some more specific things you can do.

Basic usage

First, let’s go through some of the basics before we move onto the more exciting stuff.

Getting started

First of all, create a file. In this example, I will use a file called auto.sh. It will surve no special purpose besides showing some of the functionalities that you can use in your own scripting.

Create a shebang at the top, so that the environment knows how to run the script.

#! /bin/bash

Basics to run the script.

# add execution rights
chmod +x ./auto.sh

# run the script
./auto.sh

Arguments

All arguments passed into a script (or a function) can be accessed in different ways.

# run commando
./auto.sh first second

# specific arguments
echo "$0"   # ./auto.sh
echo "$1"   # first
echo "$2"   # second

# all arguments
echo "$@"   # first second

# number of arguments
echo "$#"   # 2

Variables

Being able to save output into a variable is probably one of the most important steps. There are some different ways to save into a variable, and also to echo them out.

# save a string to a variable (no space)
NAME="Anton"

# echo the NAME variable (qoutes are preferred)
echo $NAME
echo "$NAME"
echo "${NAME}"

# echo with single qoutes will break the variable
echo 'hi $NAME' # => Hi $NAME

If an argument might be a bash command, you can execute it with the eval command.

# create command variable
COMMAND="ls -a"

# execute command
eval "$COMMAND"

Functions

Functions work like in any other languages, it’s an easy way to reuse code. The only difference is that there are no return statements. Instead, you have to echo to output and catch it outside of the function. You can add arguments to a function, exactly like above. Remeber to use the local keyword before a variable in a function, or else it will be in the global scope.

# create function with argument
greet() {
    local person="$1"

    # echo without returning
    echo "Echo value to terminal" >&2

    # echo to return value
    echo "Hello ${person}"
}

# call function
greet Anton

# save output to a variable
greeting=$(greet Anton)
echo "$greeting"    # => Hello Anton

Loops

I basically only use the for loop, which looks like this. You’ll get a better usage example below.

# loop through an array of files
for file in $FILES; do
    echo "$file"
done

Conditionals

Conditionals are… different with Bash. There are a lot of different options and possibilities, so I’ll just stick to the basics that I use the most.

# basic syntax
if [[ X == Y ]]; then
  echo "same"
fi

# file conditions
[[ -z "$STRING" ]]      # empty string
[[ -n "$STRING" ]]      # not empty string

[[ STRING == STRING ]]  # equal
[[ NUM -eq NUM ]]       # equal

[[ STRING != STRING ]]  # not equal
[[ NUM -ne NUM ]]       # not equal

[[ -e FILE ]]           # exists
[[ -f FILE ]]           # is a file
[[ -d FILE ]]           # is a directory

# example
if [[ -e "file.txt" ]]; then
  echo "file exists"
fi

In some guides, you might see people use single [ and ]. It is preferred to use double.

Some conditionals that I tend to use in my scripts are:

# see if string includes 'Anton' somewhere
if [[ "$string" == *"Anton"* ]]; then
    echo "Anton is in ${string}"
fi

# see if a variable is set to true
if [[ $WORK == true ]]; then
    echo "Is true"
else
    echo "Is false"
fi

# see if a package is installed
if [[ $(command -v grep) ]]; then
    echo "Grep already installed"
fi

Advanced usage

I think that covers some of the basics, it’s time to get into the more useful stuff. Or, at least the stuff that is not basic language syntax.

Passing argument flags into a script

Sometimes, we want to pass arguments flags like --all or maybe --greeting hello into our scripts. In this example we will pass those to into a script and see how we should handle them.

In this case:

  • --all is a boolean that specifies that we should use all of something.
  • --greeting hello is the greeting we should use, which in this case is hello.

We need to save all as a boolean and the greeting as a value, which we need to fetch with a loop.

# usage
./auto.sh --all --greeting hello

# variables to save the arguments too
ALL=false
GREETING=

# fetch the variables
while [ "$1" != "" ]; do
    case $1 in
    --all)
        ALL=true
        ;;
    -g | --greeting)
        shift
        GREETING=$1
        ;;
    *)
        echo "Invalid parameter"
        exit 1
        ;;
    esac
    shift
done

# check if parameters are set
if [[ "$GREETING" == "" ]]; then
    echo "You must provide a greeting";
    exit 1;
fi

if [[ $ALL == true ]]; then
    # all is true
else
    # all is false
fi

This might look a bit complicated. But what we basically do is loop through the first argument ($1) in the arguments array all the time. If the argument is --all we set ALL to true and then use shift to remove the first argument and replace it with the next one. You can read more about this in my blog.

Finding files

When automating things, you’ll most likely want to find some files to work with. This is a basic way to do that.

# find all .json files in: /directory/<any folder>/<any filename>.json
FILES=./directory/*/*.json

# find all .json files in the /directory folder (nested as well)
FILES=$(find ./directory -type f -name "*.json")

# loop through files
for file in $FILES; do
    # print the file content
    cat "$file"

    # save content to variable
    content=$(cat "$file")

    # content logic
    ...

    # save to new file
    echo "$new_content" >> new_file.json
done

You can read more about this subject here if you need a clearer explanation of what happens.

Replacing

Another very important part is replacing strings or variables. For example, maybe we want all the greetings in a file changed from Hi to Hello, or whatever you might fancy.

We can do this in two ways, either with string manipulation or with sed.

String manipulation uses / to replace the first occurance of the word, or // to replace all occurences.

# sentence must be a variable to work
sentence="hi everone, hi anton"
echo "${sentence/hi/hello}"     # => hello everyone, hi anton
echo "${sentence//hi/hello}"     # => hello everyone, hello anton

Sed is a bit different, but more customizable.

# you can use variables or strings
sentence="hi everone, hi anton"
sed 's/hi/hello/' <<<"hi everyone, hi anton"    # => hello everyone, hi anton
sed 's/hi/hello/g' <<<"$sentence"               # => hello everyone, hello anton

# replace in files
sed -i 's/hi/hello/g'  sentence.txt

# use another separator if you strings includes /
sed -i 's|hi|hello|g'  sentence.txt

You can read more about replacing strings here.

Example

So that is basically it, with these basics I have created quite a lot of different automation scripts. Just to finish it off, I’ll show one very basic example.

In this example, we will search for all .txt files in a folder, and if they include the word Anton, we want to replace every word first to second.

#! /bin/bash

# find all files in the current directory (and nesting) that ends with .txt
FILES=$(find . -type f -name "*.txt")

# loop through files
for file in $FILES; do
    echo "Looking through file: ${file}"

    content=$(cat "$file")

    if [[ "$content" != *"Anton"* ]]; then
        echo "Word Anton is not in script, skipping..."
        continue;
    fi

    sed -i 's/first/second/g' "$file"

    echo "File updated!"
done

echo "Done!"

The output might look something like this:

Output bash

This script might now be to useful right now, but as I said, this is just a simple example to get you up and running.