Shell Scripting: Automate Your Work with Bash
Write Bash scripts with variables, arguments, if statements, loops, functions and exit codes, and build a real backup script.
On this page 11 sections
Introduction
Anything you type in the terminal can be saved in a file and run again — that is a shell script. Scripts turn repetitive tasks into one command: backups, deployments, cleaning up logs, setting up a new machine. They are also everywhere in real projects, from Docker images to CI/CD pipelines, so reading and writing them is a core skill.
- How to create and run a script, and what the #!/bin/bash line does.
- Variables, command substitution and script arguments.
- Decisions with if, repetition with for, and reusable functions.
- Exit codes and how to make scripts fail safely.
Your First Script
#!/bin/bash# hello.sh: variables and command substitutionname="Priya"today=$(date +%A)echo "Hello, $name! Today is $today."echo "You are in $(pwd)"The first line, #!/bin/bash (the “shebang”), tells Linux which program should run the file. Lines starting with # are comments. name="Priya" sets a variable, and $(date +%A) is command substitution: it runs date +%A and puts its output (the day of the week) into the text. Make the script executable and run it:
chmod +x hello.sh./hello.shClick Run to see what this code prints.
Write "$name", not $name. Without quotes, a value containing spaces (like a file called my report.txt) is split into separate words and the script breaks in confusing ways.
Arguments and if
Words after the script name are arguments: $1 is the first, $2 the second, $# how many there are, and $0 the script’s own name. if makes decisions, using tests inside [ ]:
#!/bin/bash# greet.sh: arguments and if/elseif [ $# -eq 0 ]; then echo "Usage: $0 <name>" exit 1fi
name="$1"if [ "$name" = "admin" ]; then echo "Welcome back, administrator."else echo "Hello, $name!"fi./greet.shecho "exit code: $?"Click Run to see what this code prints.
./greet.sh Arjun./greet.sh adminClick Run to see what this code prints.
| Test | True when |
|---|---|
| [ "$a" = "$b" ] | Two strings are equal (!= for not equal) |
| [ -z "$a" ] | A string is empty |
| [ $n -eq 0 ] | Numbers are equal (-ne, -lt, -le, -gt, -ge for others) |
| [ -f file ] | A regular file exists |
| [ -d folder ] | A folder exists |
| [ ! -d folder ] | A folder does not exist (! means not) |
[ "$a" = "$b" ] needs spaces after [ and before ]. Writing [“$a”=“$b”] is a syntax error, because [ is actually a command and its parts are separate arguments.
Loops and Arithmetic
A for loop repeats commands for each item in a list — here, every .txt file. $(( )) does arithmetic:
#!/bin/bash# count-lines.sh: loop over files and do arithmetictotal=0for file in *.txt; do lines=$(wc -l < "$file") echo "$file: $lines lines" total=$((total + lines))doneecho "Total: $total lines"./count-lines.shClick Run to see what this code prints.
Bash also has while loops, which repeat as long as a condition is true — commonly used to read a file line by line with while read -r line; do …; done < file.txt.
This line in a script fails for the file “my report.txt” but works for “report.txt”: cp $file backups/. What is the problem?
A Real Script: Backups
Here is a complete, practical script that saves a folder as a dated archive. It checks its input, uses a function, and reports errors properly:
#!/bin/bash# backup.sh: save a folder as a dated .tar.gz archiveset -euo pipefail
backup_folder() { local source="$1" local dest="$HOME/backups" local name name="$(basename "$source")-$(date +%Y%m%d-%H%M%S).tar.gz" mkdir -p "$dest" tar -czf "$dest/$name" -C "$(dirname "$source")" "$(basename "$source")" echo "Backed up $source to $dest/$name"}
if [ $# -ne 1 ]; then echo "Usage: $0 <folder>" >&2 exit 1fiif [ ! -d "$1" ]; then echo "Error: '$1' is not a folder" >&2 exit 2fi
backup_folder "$1"backup_folder() { … }defines a function;localkeeps its variables private to it.tar -czfcreates a compressed .tar.gz archive;-Cmakes the archive store the folder by its own name rather than its full path.>&2sends error messages to stderr, andexit 1/exit 2report what went wrong.
./backup.shecho "exit code: $?"Click Run to see what this code prints.
./backup.sh nothing-hereecho "exit code: $?"Click Run to see what this code prints.
./backup.sh websiteecho "exit code: $?"ls backupsClick Run to see what this code prints.
tar -tzf lists what is inside an archive without extracting it, so you can check a backup:
tar -tzf backups/website-*.tar.gzClick Run to see what this code prints.
Exit Codes, && and ||
Every command finishes with an exit code: 0 means success, anything else is an error. && runs the next command only if the previous one succeeded, and || runs it only if the previous one failed:
mkdir reports && echo "created reports"mkdir reports && echo "created again" || echo "could not create reports"Click Run to see what this code prints.
Put the steps of creating and running a new script in order.
- Run it with ./script.sh
- Make it executable with chmod +x script.sh
- Check the result and the exit code with echo $?
- Write the commands and save the file
- Create the file, starting with #!/bin/bash
Writing Safer Scripts
By default Bash keeps going after a command fails, which can turn one error into a disaster. The line set -euo pipefail near the top of backup.sh changes that:
| Option | Effect |
|---|---|
| -e | Stop the script as soon as a command fails |
| -u | Treat using an unset variable as an error (catches typos) |
| -o pipefail | Make a pipeline fail if any command in it fails, not just the last |
ShellCheck (sudo apt install shellcheck, or the website shellcheck.net) finds common bugs such as missing quotes and explains how to fix them. Running it on every script is an excellent habit.
Common Mistakes
- Leaving variables unquoted, so names with spaces break the script.
- Writing spaces around = when setting a variable.
- Forgetting the shebang line or chmod +x, then getting “Permission denied” or odd errors.
- Running destructive commands like rm -rf "$DIR/" with a variable that might be empty — set -u protects you.
Frequently Asked Questions
Bash is ideal for gluing commands together and automating file and system tasks. Once a script needs complex data structures, lots of logic or error handling, Python is usually easier to read and maintain.
./script.sh needs execute permission and uses the shebang line; bash script.sh runs the file with Bash directly and works even without execute permission.
With cron, covered in lesson 14 and in the Automated Backups project.
Key Takeaways
- A script starts with #!/bin/bash, needs chmod +x and runs as ./script.sh.
- Variables: name="value", used as "$name"; $(command) captures output.
- $1, $2… are arguments, $# counts them; if [ … ] decides; for loops repeat.
- Every command has an exit code (0 = success); use exit codes, && and || to handle errors.
- Start scripts with set -euo pipefail and quote every variable.
Summary
You can now automate tasks with real, robust Bash scripts. Next you will reach beyond your own computer: checking network connections, downloading with curl and wget, and working on remote servers over SSH.