Scripts are used to automate tasks and provide human-readable and portable way to do things.

Basics

It's expected that scripts will have .sh extension and executable bit set. You can check it using: if [[ -f path && -x path ]]; then echo executable; fi Setting it can be done using: chmod +x path Scripts without executable bit set can still be run from SystemD services or by passing path to it to bash or other shell.

First line of script should start with shebang (#! character sentence) followed by command that should be used to determine what program should be used to execute script. For bash you would expect first line of script to be #!/usr/bin/env bash Variant used in past - #!/bin/bash - is not always present or bash.

Double quote symbol (") can be used to create string of characters, potentially with values of variables or command outputs.

Single quote symbol (') can be used to create string of characters.

Streams

Script can redirect of command from one command directly to another. Typically it's recommended to use streams instead of temporary files (this allows skipping disk write then read).
| pipe standard output of previous command to next one
> redirects standard output to specified path (overwrites content)
>> redirects standard output to specified path (appends to file's content)
< takes standard input from specified file/command
2>&1 redirects standard error to where standard output goes - portable, recommended method of processing standard error
2>/dev/null redirects standard error to special null device (to discard it)
&> redirects standard output and error to specified path (overwrites content) - non portable,not recommended
&>> redirects standard output and error to specified path (appends to file's content) - not portable, not recommended

Loops

Loops can be used to perform work multiple times. One can loop over arrays or ad hoc collections Ad hoc example:
for i in {1..3}{a,b} 4 "5 6";do echo "${i}";done
output:
1a
1b
2a
2b
3a
3b
4
5 6
Example of looping over file lines rather than words:
while IFS= read -r line;do echo $line;done < <(cat file)
output:
1
2 3
Setup for example above:
echo '1
2 3' > file

Functions

Functions can be used to execute desired code multiple time, with different parameters (or not)
function demo() {
  echo "first: ${1}, second: ${2}"
}
demo
demo 1 2 3
demo "1 2"
output:
first: , second:
first: 1, second: 2
first: 1 2, second: 

Variables

Variables can be used to store values for later use

Declaring variables

declare foo="item1 item2 item3" Alternative foo="item1 item2 item3"

Checking state of variable foo

if [ ! -v foo ]
then
	echo "Variable foo was not set"
elif [ -z "$foo" ]
then
	echo "Variable foo is set to an empty string"
else
	echo "Variable foo is set"
fi

Using variables

echo "${foo}" Passing to function (example read_var displays value of variable)
function read_var() {
	for i in "$@"
	do
		echo "$i"
	done
}
declare foo=("item1" "item2" "item3")
read_var "${foo}"

Arrays

Declaring arrays

declare -a array_var=("item1" "item2" "item3") Alternative array_var=("item1" "item2" "item3")

Checking size of array

echo "${#array_var[@]}"

Using arrays

  1. Accessing item under specific index (in this example, second item - first item is under index 0) echo "${array_var[1]}"
  2. Safely iterating over elements of arrays (double quotes around variable name block are not optional - without them, for will split on whitespaces in array elements)
    for i in "${array_var[@]}"
    do
        echo "$i"
    done
    One-liner: for i in "${array_var[@]}"; do echo "$i"; done
  3. Passing arrays to function (example iterate_over_array iterates over array elements)
    function iterate_over_array() {
    	for i in "$@"
    	do
    		echo "$i"
    	done
    }
    declare -a array_var=("item1" "item2" "item3")
    iterate_over_array "${array_var[@]}"
    

Conditional statements

Allows matching actions to condition

General syntax

if condition1
then
	statements1
elif condition2
then
	statements2
else
	fallbackStatements
fi
Single line syntax: if condition1; then statements1; elif condition2; then statements2; else fallbackStatements; fi

Examples

if [[ $foo > 123 ]]
then
	echo "$foo is more than 123"
elif [[ $foo = 123 ]]
then
	echo "$foo is 123"
else
	echo "$foo is not 123 or greater number"
fi
Single condition check if [[ $foo > 123 ]]; then echo "$foo is more than 123"; fi

Case statement

Allows multi-pattern conditional statement

General syntax

case expression_or_variable in
pattern1)
	statements1
	;;
pattern2)
	statements2
	;;
patternX)
	statementsX
	;;
*)
	fallbackStatements
	;;
esac
Single line general syntax: case expression_or_variable in pattern1) statements1 ;; pattern2) statements2 ;; patternX) statementsX ;; *) fallbackStatements ;; esac

Example

case $foo in
123)
	echo "$foo matches pattern 123"
	;;
100 | "a b")
	echo "$foo matches pattern 100 or \"a b\""
	;;
1?3)
	echo "$foo matches pattern 1?3"
	;;
1*3)
	echo "$foo matches pattern 1*3"
	;;
*)
	echo "$foo does not match other patterns"
	;;
esac

Comparison

  1. Checking is value of $foo is equal to 123 if [[ $foo == 123 ]]; then echo equal; fi Checking is value of $foo is equal to 123 - integers only if [ $foo -eq 123 ]; then echo equal; fi Checking is value of $foo is equal to item1 item2 item3 if [[ $foo == 'item1 item2 item3' ]]; then echo equal; fi
  2. Checking is value of $foo is less than 123 if [[ $foo < 123 ]]; then echo equal; fi Checking is value of $foo is less than 123 - only integers if [ $foo -lt 123 ]; then echo equal; fi
  3. Checking is value of $foo is greater than 123 if [[ $foo > 123 ]]; then echo equal; fi Checking is value of $foo is greater than 123 - only integers if [ $foo -gt 123 ]; then echo equal; fi

shellcheck

Tool useful for script syntax and logic verification. Usage:

shellcheck path_to_script_file

This isn't antivirus or execution safety checker. It will not complain if script aim to erase all data with no way to recover it. It just provides hints on what could be done to improve likelihood of successful execution of script based on common errors and bad practices.