Skip to content

Short Guide to Bash Scripting

Published: at 08:30 PM

Introduction

Bash scripting is a fundamental skill for Linux and Unix system administrators, developers, and power users alike. Whether you’re automating repetitive tasks, creating complex workflows, or managing system configurations, Bash scripting empowers you to wield the full potential of the command line.

In this cheat sheet, we’ll dive into the essential concepts, syntax, and best practices of Bash scripting. From basic commands to advanced techniques, this guide serves as a handy reference for both beginners and seasoned Bash enthusiasts.

Unlock the power of automation, streamline your workflow, and unleash your productivity with Bash scripting.

TL;DR

You can find a shorter cheat sheet version of this article here.

Table of contents

Open Table of contents

Variables

Variables serve as the building blocks that enable dynamic and flexible behavior in your scripts. Whether you’re storing data, manipulating strings, or controlling program flow, understanding Bash variables is essential for crafting robust and efficient scripts.

First, lets define a variable:

name="John"

Use the variable:

echo $name
echo "$name"
echo "${name}!"

Substitution

String substitution in Bash allows you to replace parts of a string with another string:

original_string="Hello, World!"
new_string="${original_string/Hello/Hi}"
echo "$new_string"  # Output: Hi, World!

Slicing

String slicing in Bash involves extracting a portion of a string based on its indices. Unlike some other programming languages, Bash does not have built-in support for string slicing, but you can achieve similar functionality using parameter expansion and sub-string extraction:

${string:start:length}

where start is the starting index (0-based) and length is the number of characters to extract.

Examples:

echo "${name:0:2}"    # "Jo" (slicing)
echo "${name::2}"     # "Jo" (slicing)
echo "${name::-1}"    # "Joh" (slicing)
echo "${name:(-1)}"   # "n" (slicing from right)
echo "${name:(-2):1}" # "h" (slicing from right)

Changing case

In Bash, you can easily change the case of strings using parameter expansion:

echo "${str,}"  # lowercase 1st letter
echo "${str,,}" # all lowercase
echo "${str^}"  # uppercase 1st letter
echo "${str^^}" # all uppercase

Default values

In Bash scripting, you can assign default values to variables using parameter expansion. This is useful when you want to ensure that a variable has a value even if it’s not explicitly set.

${foo:-val}     # $foo, or val if unset (or null)
${foo:=val}     # set $foo to val if unset (or null)
${foo:+val}     # val if $foo is set (and not null)
${foo:?message} # show error message and exit if $foo is unset (or null)

Special variables

Special variables in Bash are predefined variables that hold specific information or have special meanings in the context of a Bash script. These variables are denoted by special characters or predefined names and provide valuable information about the script’s environment, command-line arguments, and execution status. Here are some commonly used special variables in Bash:

$? # exit status of last task
$! # PID of last background task
$$ # PID of shell
$0 # filename of the shell script
$_ # last argument of the previous command

Comments

Comments are lines in a script that are ignored by the interpreter and serve as notes or explanations for humans reading the code. They are used to document the purpose of specific code segments, provide instructions, or add clarifications to the script. Comments are not executed and do not affect the functionality of the script.

In Bash scripting, comments can be written in two ways:

Single line:

# Single line comment

Multi line:

: '
This is a
multi line
comment
'

Loops

Loops in Bash allow you to execute a set of commands repeatedly until a specified condition is met. There are mainly three types of loops in Bash:

For loop

Executes a sequence of commands for each item in a list or range.

for VARIABLE in 1 2 3 4 5 .. N
do
  ...
done

Examples of for loop:

for i in {1..10}; do
  echo $i;
done
for i in $(seq 1 10); do
  echo $i;
done

C-like version:

for ((i = 0 ; i < 100 ; i++)); do
  echo "$i"
done

While loop

Executes a sequence of commands as long as a given condition is true.

while [CONDITION]; do
  ...
done

Examples:

Read file line by line:

while read -r line; do
  echo "$line"
done < file.txt

Infinite loop:

while true; do
  ···
done

Until loop

Executes a sequence of commands until a given condition becomes true.

until [CONDITION]
do
  ...
done

Example:

counter=0

until [ $counter -gt 10]
do
  echo Counter: $counter
  ((counter++))
done

These loops are fundamental for automating repetitive tasks, iterating over collections of data, and controlling program flow in Bash scripts.

Functions

Bash functions are reusable blocks of code that perform specific tasks. They allow you to encapsulate a sequence of commands into a single entity, making your scripts more modular, readable, and easier to maintain. Functions can accept arguments, execute commands, and return values. They are particularly useful for organizing and structuring Bash scripts, as well as for reducing code duplication.

Define function

function hello {
  echo "hello world"
}

alternate syntax:

hello() {
  echo "hello world"
}

one-liner:

function function_name { command; }

Function arguments

Function arguments, also known as parameters, are values that are passed to a function when it is called. They provide a way to pass data into the function so that it can operate on different inputs each time it is invoked.

$# # number of arguments
$1 # first argument
$2 # second argument
$n # nth argument
$* # all positional arguments (as a single word)
$@ # all positional arguments (as separate strings)
$_ # last argument of the previous command

Conditions

Conditions are used to make decisions based on the evaluation of expressions or the outcome of commands. They enable the script to take different paths or execute different blocks of code depending on whether a condition is true or false. Bash conditions are typically used with conditional statements such as if, elif (else if), and else.

Files

Test conditions related to files are used to check various properties or attributes of files and directories:

[[ -e FILE ]]         # file exists
[[ -r FILE ]]         # file is readable
[[ -h FILE ]]         # is symlink
[[ -d FILE ]]         # is directory
[[ -w FILE ]]         # file is writable
[[ -s FILE ]]         # size is > 0 bytes
[[ -f FILE ]]         # file
[[ -x FILE ]]         # executable
[[ FILE1 -ef FILE2 ]] # same files

Examples:

if [[ -e "file.txt" ]]; then
  echo "File exists"
fi
if [ -d "$dirname" ]; then
    echo "Directory exists"
fi
if [ -r "$filename" ]; then
    echo "File is readable"
fi
if [ -w "$filename" ]; then
    echo "File is writable"
fi
if [ -x "$filename" ]; then
    echo "File is executable"
fi
if [ ! -s "$filename" ]; then
    echo "File is empty"
fi

Strings

String-related conditions are used to compare strings, check their lengths, and perform various operations based on string properties:

[[ -z STRING ]]          # empty
[[ -n STRING ]]          # not empty
[[ STRING1 == STRING2 ]] # equal
[[ STRING1 != STRING2 ]] # not Equal

Examples:

if [ "$str1" != "$str2" ]; then
    echo "Strings are not equal"
fi
if [ "$str1" != "$str2" ]; then
    echo "Strings are not equal"
fi
if [[ -z "$string" ]]; then
  echo "String is empty"
else
  echo "String is not empty"
fi
if [ -n "$str" ]; then
    echo "String is not empty"
fi
if [[ "$str" == *"sub"* ]]; then
    echo "String contains 'sub'"
fi
if [ ${#str} -gt 5 ]; then
    echo "String length is greater than 5"
fi

Numbers

Number-related conditions in Bash are used to perform comparisons between numeric values:

[[ NUM1 -eq NUM2 ]] # equal
[[ NUM1 -ne NUM2 ]] # not equal
[[ NUM1 -lt NUM2 ]] # less than
[[ NUM1 -le NUM2 ]] # less than or equal
[[ NUM1 -gt NUM2 ]] # greater than
[[ NUM1 -ge NUM2 ]] # greater than or equal

Example:

if [[ $n -gt $n2 ]]; then

elif [[ $n -eq $n2 ]]; then

else

fi
num1=10
num2=20
if [ "$num1" -eq "$num2" ]; then
    echo "Numbers are equal"
fi
num1=10
num2=20
if [ "$num1" -ne "$num2" ]; then
    echo "Numbers are not equal"
fi
num1=30
num2=20
if [ "$num1" -gt "$num2" ]; then
    echo "Number 1 is greater than number 2"
fi
num1=20
num2=20
if [ "$num1" -le "$num2" ]; then
    echo "Number 1 is less than or equal to number 2"
fi

Logical operators

Logical operators are used to perform logical operations on expressions

[[ ! X ]]    # not
[[ X && Y ]] # and
[[ X || Y ]] # or

Examples:

age=25
if [ "$age" -gt 18 ] && [ "$age" -lt 30 ]; then
    echo "Age is between 18 and 30"
fi
day="Saturday"
if [ "$day" == "Saturday" ] || [ "$day" == "Sunday" ]; then
    echo "It's a weekend!"
fi
is_raining=false
if ! $is_raining; then
    echo "It's not raining"
fi

Case

The case statement provides a way to execute different blocks of code based on the value of a variable. It’s similar to the switch statement in other programming languages.

case "$1" in
start)
  echo "start"
  ;;
stop)
echo "stop"
  ;;
  *)
echo "Usage: $0 {start|stop}"
  ;;
esac
day="Sunday"
case $day in
    "Monday" | "Tuesday" | "Wednesday" | "Thursday" | "Friday")
        echo "It's a weekday." ;;
    "Saturday" | "Sunday")
        echo "It's a weekend." ;;
    *) # Default case
        echo "Invalid day." ;;
esac
file="example.txt"
case $file in
    *.txt)
        echo "Text file detected." ;;
    *.jpg | *.jpeg | *.png)
        echo "Image file detected." ;;
    *)
        echo "Unknown file type." ;;
esac

Redirections

Redirections are used to control the input and output of commands. Here’s an overview of redirections with examples:

./program > output.txt     # stdout to file
./program >> output.txt    # append stdout to file
./program 2> error.log     # stderr to file
./program 2>&1             # stderr to stdout
./program 2>/dev/null      # stderr to /dev/null
./program >output.txt 2>&1 # stdout and stderr to file, equivalent to &>
./program &>/dev/null      # stdout and stderr to /dev/null
./program < input.txt      # stdin from input.txt

Redirects the standard output of a command to a file or device:

echo "Hello, World!" > output.txt

Appends the standard output of a command to a file:

ps aux >> processes.txt

Redirects the standard input of a command from a file:

grep "keyword" < input.txt

Redirects standard error output to a file or device:

ls /nonexistent 2> errors.txt
ls /nonexistent &> combined_output.txt

Redirects the output of one command as input to another command:

ls -l | grep "file"

Heredoc

A here document (heredoc) in Bash is a way to pass multiline input to a command or script. It allows you to specify a block of text within your script directly, without needing to escape special characters or worry about quoting issues.

command << delimiter
text
delimiter

Example:

cat <<END

END

Shell command execution

Enclosing a command within backticks executes the command and substitutes its output (obsolete syntax):

result=`ls -l`
echo $result

Similar to backticks, but with improved readability and nesting capabilities:

result=$(ls -l)
echo $result

Executing a command inline within another command or string:

echo "Current directory $(pwd)"
echo "Current directory `pwd`"

Executes a command or set of commands stored in a string variable:

command="ls -l"
eval $command

Conclusion

In conclusion, mastering Bash scripting opens up a world of possibilities for automating tasks, managing system configurations, and enhancing productivity on the command line. By understanding variables, loops, functions, conditions, and other essential concepts covered in this article, you can create powerful scripts to streamline your workflow and solve complex problems efficiently.

While this article provides a solid foundation for getting started with Bash scripting, remember that there is always more to learn. Explore advanced topics, experiment with different techniques, and leverage the vast resources available online, including forums, tutorials, and documentation.

Whether you’re a system administrator, developer, or just someone looking to make their command line experience more productive, Bash scripting is a valuable skill to have in your toolkit. With practice and perseverance, you’ll become proficient in writing Bash scripts that automate tasks, simplify workflows, and unleash the full potential of the Linux command line.