Advanced Bash Scripting
Refresher
Scriptflow
Understanding and controlling your shell script’s execution path is crucial for writing reliable Bash scripts. By default, a script runs sequentially, but you can alter this sequence using control constructs such as conditionals, loops, sourcing files, and functions. These tools allow you to:
- Run commands only if specific conditions are met
- Repeat commands multiple times with varying inputs
A Real-World Analogy: Buying a Movie Ticket
Imagine you walk up to a theater ticket booth. If you hand over a valid ticket, you enter; if not, you’re turned away. This decision-making process mirrors how an if
statement in Bash evaluates conditions and branches accordingly.
A Factory Analogy for Complex Workflows
Consider a widget factory where each item travels along a conveyor. At an inspection station, defective widgets are removed while good ones proceed to the next stage. This inspection step functions like a control construct in your script, deciding whether data moves forward or is handled differently.
Key Constructs That Alter Scriptflow
Shell scripts use these four core constructs to modify the default linear execution:
Construct | Purpose | Example Syntax |
---|---|---|
Conditional (if , case ) | Branch logic based on conditions | if [[ $x -gt 5 ]]; then … fi |
Loop (for , while , until ) | Repeat code blocks until a condition changes | for i in {1..3}; do … done |
Sourcing External Files | Include and execute another script at runtime | source config.sh |
Function | Encapsulate and reuse code segments | my_func() { echo "Hi"; } |
Conditional Statements
if
Statement
Bash’s [[ … ]]
test command provides richer conditional checks than [ … ]
:
#!/usr/bin/env bash
# This block never runs because 3 is not greater than 4
if [[ 3 -gt 4 ]]; then
echo "This will never be printed"
fi
Note
We recommend [[ … ]]
over [ … ]
for its support of pattern matching and logical operators.
case
Statement
Use case
for clear branching when matching a variable against multiple patterns:
#!/usr/bin/env bash
action="$1"
case "$action" in
start)
echo "Starting service";;
stop)
echo "Stopping service";;
restart)
echo "Restarting service";;
*)
echo "Usage: $0 {start|stop|restart}";;
esac
Loops
Loops are ideal for executing commands multiple times, either a fixed count or until a condition changes.
Here are five versatile loop patterns:
while
loop with a counter#!/usr/bin/env bash i=1 while [[ $i -le 3 ]]; do echo "Iteration $i" i=$(( i + 1 )) done
for
loop with brace expansion#!/usr/bin/env bash for i in {1..3}; do echo "Iteration $i" done
until
loop counting down#!/usr/bin/env bash i=3 until [[ $i -eq 0 ]]; do echo "Iteration $i" i=$(( i - 1 )) done
- Piping
seq
intowhile
#!/usr/bin/env bash seq 1 3 | while read -r i; do echo "Iteration $i" done
- Reading lines from a file
#!/usr/bin/env bash while read -r line; do echo "Line: $line" done < fds.txt
Note
Use for
loops when the number of iterations is predetermined.
Note
Choose while
or until
when waiting for a dynamic condition or external event.
Sourcing External Files
You can import another script or configuration file mid-execution using source
or the shorthand .
. This merges the external content into your current shell environment.
Example v1: Basic Sourcing
# .conf content:
#!/usr/bin/env bash
source .conf
echo "${name}"
$ ./conf_read-v1.sh
Bob Doe
Example v2: Safe Sourcing with Fallback
#!/usr/bin/env bash
readonly CONF_FILE=".conf"
if [[ -f "${CONF_FILE}" ]]; then
source "${CONF_FILE}"
else
name="Bob"
fi
echo "${name}"
exit 0
$ ./conf_read-v2.sh
Bob
When .conf
exists:
$ echo 'name="Juan Carlos"' > .conf
$ ./conf_read-v2.sh
Juan Carlos
Warning
Always verify or sanitize sourced files to avoid executing untrusted code.
Next Steps
In the next section, we explore how functions can modularize your scriptflow, making your Bash scripts more maintainable and reusable.
Links and References
Watch Video
Watch video content