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.