Chapter Objective: Write and run basic bash shell scripts using variables, positional parameters, exit status, conditionals, loops, and functions to automate repetitive command-line tasks.
Key concepts: #!/bin/bash, $1/$@, $?, if/test, for/while, functions
Why Write Shell Scripts?
A shell script is a text file containing a sequence of shell commands, run together as a single program. Anything you can type at the prompt, you can put in a script — the difference is repeatability: a script runs the same steps, in the same order, every time, without you retyping them.
Anatomy of a Script
#!/bin/bash
# This is a comment — bash ignores everything after #
echo "Starting backup..."
tar -czf /backups/etc-$(date +%Y%m%d).tar.gz /etc
echo "Backup complete."
| Line | Purpose |
|---|---|
#!/bin/bash | The shebang — tells the system which interpreter should run this file |
# comment | Explanatory text, ignored when the script runs |
| Everything else | Ordinary shell commands, run top to bottom |
# Make the script executable
chmod +x backup.sh
# Run it directly (relies on the shebang)
./backup.sh
# Or run it explicitly with bash, without needing execute permission
bash backup.sh
#!/bin/bash, the system won't recognize it as a shebang and will fail to run the script as expected via ./scriptname.
Variables and Parameters
#!/bin/bash
# Assign a variable — no spaces around the equals sign
name="student"
# Reference it with a dollar sign
echo "Hello, $name"
# Curly braces avoid ambiguity when concatenating
echo "${name}s backup file"
Positional Parameters
Arguments passed to a script are available as numbered variables.
#!/bin/bash
# ./greet.sh Miguel Sales
echo "First argument: $1" # Miguel
echo "Second argument: $2" # Sales
echo "All arguments: $@"
echo "Number of arguments: $#"
echo "Script name: $0"
"$1" (quoted) behaves predictably even if the argument contains spaces; unquoted $1 can split unexpectedly. Get in the habit of quoting variable references in scripts.
Exit Status
Every command returns a numeric exit status when it finishes: 0 means success, any nonzero value means some kind of failure. Scripts use this constantly to make decisions.
# Check the exit status of the last command
grep root /etc/passwd
echo $?
# 0 if found, 1 if not found
# Explicitly set a script's own exit status
#!/bin/bash
echo "Checking..."
exit 0 # success
$? only reflects the immediately preceding command — check it right away, before running anything else, or the value you're reading will belong to a different command.
Conditionals
#!/bin/bash
if [ -f /etc/hostname ]; then
echo "The file exists."
elif [ -d /etc/hostname ]; then
echo "That's actually a directory."
else
echo "Not found."
fi
| Test | True When |
|---|---|
-f file | File exists and is a regular file |
-d dir | Directory exists |
-z string | String is empty |
-eq / -ne | Numeric equal / not equal |
= / != | String equal / not equal |
#!/bin/bash
# Numeric comparison example
count=5
if [ "$count" -gt 3 ]; then
echo "count is greater than 3"
fi
[ -f /etc/hostname ] needs spaces immediately inside the brackets — [-f /etc/hostname] or [ -f /etc/hostname] will fail with a syntax error. The brackets are actually a command (test) that needs to be separated from its arguments.
Loops
#!/bin/bash
# for loop over a list of values
for user in alice bob carol; do
echo "Creating account for $user"
done
# for loop over files matching a pattern
for file in /etc/*.conf; do
echo "Found config: $file"
done
# while loop — runs as long as the condition is true
count=1
while [ "$count" -le 5 ]; do
echo "Attempt $count"
count=$((count + 1))
done
+ the way you'd expect. Wrap arithmetic expressions in $(( )), as in count=$((count + 1)), to get the calculation instead of string concatenation.
Functions
#!/bin/bash
# Define a function
greet() {
echo "Hello, $1"
}
# Call it, passing an argument just like a script
greet "Miguel"
# Functions can return an exit status with return
check_file() {
if [ -f "$1" ]; then
return 0
else
return 1
fi
}
if check_file /etc/hostname; then
echo "File check passed"
fi
return value is an exit status (0–255), not a general-purpose return value like in most programming languages. To send back actual data, have the function echo it and capture that with command substitution instead.
Key Terms for Chapter 1
- shell script
- A text file containing a sequence of shell commands, run together as a program
- shebang
- The
#!line at the top of a script specifying which interpreter should run it - positional parameter
- A numbered variable (
$1,$2, ...) holding an argument passed to a script - exit status
- The numeric result a command returns on completion; 0 means success
- test / [ ]
- The command (and its bracket shorthand) used to evaluate conditions in a script
- function
- A named, reusable block of shell commands, defined once and callable by name
Review Questions
- What is a shebang, and what happens if it isn't the very first line of a script?
- In a script called with
./deploy.sh production 2, what would$1and$2each contain? - What does an exit status of
0conventionally mean, and how would you check the exit status of the last command you ran? - What is wrong with writing a conditional as
if [-f /etc/hostname]; then? - What is the difference between a
forloop and awhileloop, in terms of when each one stops? - Why doesn't
count = count + 1work the way you might expect in bash, and what syntax fixes it? - How does a bash function send back an actual piece of data (like a string) to the code that called it, given that
returnonly supports numeric exit statuses?