Bash · lesson 1 of 22
Introduction to Bash
Understand what Bash is, how it differs from a terminal and a shell, and why it is the backbone of DevOps, CI and automation work.
By SkillByExample · Updated
What Is Bash?
Bash stands for Bourne Again SHell. It was written by Brian Fox for the GNU Project in 1989 as a free replacement for the original Bourne shell (sh). Today it ships as the default interactive shell on virtually every Linux distribution and was the default on macOS until Catalina (which switched to zsh). Understanding Bash means understanding the language that underlies the majority of server automation, CI/CD pipelines, and DevOps tooling in use today.
Bash is two things at once:
- A command language — a programming language designed around running programs and manipulating files.
- An interactive shell — the program that reads your keystrokes, interprets them, and prints results.
Terminal vs Shell vs Bash
These three terms are often used interchangeably but they mean different things. Mixing them up leads to confusion when you’re trying to troubleshoot why a script behaves differently in different environments.
| Term | What it actually is |
|---|---|
| Terminal | The window/application (iTerm2, GNOME Terminal, Windows Terminal). It handles rendering text and capturing keyboard input. |
| Shell | The interpreter running inside the terminal. It reads commands and executes them. Examples: bash, zsh, fish, dash. |
| Bash | One specific shell implementation — the one you’ll use on almost every Linux server in production. |
Think of the terminal as the TV set and the shell as the channel. Bash is one specific channel.
Why Bash Matters in DevOps and Automation
CI/CD Pipelines
Every GitHub Actions workflow, GitLab CI job, and Jenkins build step ultimately runs shell commands. Even when the pipeline YAML looks high-level, the runner drops into bash to execute your steps. Knowing Bash means you can write, debug, and optimize these steps directly rather than treating them as a black box.
# .github/workflows/deploy.yml (the run: block is bash)
- name: Build and push Docker image
run: |
docker build -t myapp:${{ github.sha }} .
docker push myapp:${{ github.sha }}
Server Provisioning and Bootstrap
When a cloud VM first boots, it runs a bash script to install packages, configure services, and prepare the environment. AWS EC2 user-data, GCP startup scripts, and Azure custom script extensions are all bash. This script runs before any configuration management tool like Ansible or Chef, making it the first line of server setup.
#!/usr/bin/env bash
# EC2 user-data: runs once on first boot
set -euo pipefail
apt-get update -y
apt-get install -y nginx
systemctl enable --now nginx
echo "Bootstrap complete" >> /var/log/bootstrap.log
Automation and Scripting
Bash is the glue language of Unix. It orchestrates tools like grep, awk, sed, curl, and jq into workflows that would take hundreds of lines in a general-purpose language. The ability to compose these tools into pipelines is one of the most powerful ideas in computing.
# Find all services returning non-200 status codes
while IFS= read -r url; do
status=$(curl -s -o /dev/null -w "%{http_code}" "$url")
[[ "$status" != "200" ]] && echo "ALERT: $url returned $status"
done < services.txt
Log Analysis
Production engineers spend significant time parsing logs. Bash pipelines with grep, awk, and sort can answer complex questions in seconds without loading data into a database. The ability to query logs directly on the server is a critical skill for incident response.
# Top 10 IPs hitting your nginx server
awk '{print $1}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head -10
How Bash Fits Into the Bigger Picture
Understanding the execution chain helps you reason about where failures occur and what context your scripts run in.
User types command
↓
Terminal (renders UI)
↓
Bash (interprets command)
↓
Kernel (executes system calls)
↓
Hardware
Bash sits between you and the operating system kernel. It translates human-readable commands into the system calls that create processes, open files, and move data.
Bash vs Other Shells
Bash is not the only shell, but it is the most widely deployed on Linux servers. Knowing where the alternatives fit helps you make the right choice for each context.
| Shell | Notes |
|---|---|
| bash | Default on Linux, available everywhere, POSIX-compatible plus extras |
| zsh | Default on macOS since Catalina; better interactive features, compatible with most bash scripts |
| fish | User-friendly, great autocomplete, but intentionally not POSIX-compatible |
| dash | Minimal, fast, strict POSIX — used as /bin/sh on Debian/Ubuntu for boot scripts |
| ksh | Korn shell, common in enterprise Unix (AIX, Solaris) |
For scripting that runs on servers, always target bash explicitly — do not rely on /bin/sh unless you intentionally want POSIX-only portability.
Your First Bash Command
The best way to get comfortable with Bash is to start running commands. Open a terminal and type the three commands after each $ prompt. Here is what they printed on Ubuntu 24.04:
$ echo "Hello, Bash!"
Hello, Bash!
$ which bash
/usr/bin/bash
$ bash --version
GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
which prints the first bash on your PATH. On some systems it’s /bin/bash, and on an Apple-silicon Mac with Homebrew it’s /opt/homebrew/bin/bash. The version line is the one that matters: this track uses bash 5.2, the version Ubuntu 24.04 ships. Bash 5.3 is the current release. Where it behaves differently, the lesson says so. macOS still ships bash 3.2 as /bin/bash, which lacks features later lessons use, such as associative arrays. If that’s what you see, the setup lesson shows how to install a current bash.
What Bash Is Not Good At
Knowing when not to use Bash is just as important as knowing how to use it. Reach for Python, Go, or another language when you need:
- Complex data structures (hash maps of arrays, nested objects)
- HTTP clients with proper error handling and JSON parsing
- Floating-point arithmetic (Bash only does integers natively)
- Code that needs to be unit tested and maintained long-term by a team
The rule of thumb: if your bash script exceeds ~200 lines or needs real error handling beyond set -e, consider rewriting it in Python.
Practice
-
Put the three commands from “Your First Bash Command” into a file named
first.sh, run it withbash, and add a fourth line that prints the version from inside the running shell rather than by starting a new one.Solution
Bash keeps its own version in the
BASH_VERSIONvariable:echo "Hello, Bash!" which bash bash --version | head -1 echo "Running bash $BASH_VERSION"$ bash first.sh Hello, Bash! /usr/bin/bash GNU bash, version 5.2.21(1)-release (x86_64-pc-linux-gnu) Running bash 5.2.21(1)-releasebash --versionstarts a second bash process just to ask it.$BASH_VERSIONis the version of the shell that is already running your script. Here they match because there is only one bash installed. -
Save these six lines as
access.log, then change the “Top 10 IPs” pipeline so it reads this file and shows only the top 2.10.0.0.5 - - [11/Oct/2026:10:00:01 +0000] "GET / HTTP/1.1" 200 512 10.0.0.7 - - [11/Oct/2026:10:00:02 +0000] "GET /login HTTP/1.1" 200 230 10.0.0.5 - - [11/Oct/2026:10:00:03 +0000] "GET /api HTTP/1.1" 500 87 10.0.0.9 - - [11/Oct/2026:10:00:04 +0000] "GET / HTTP/1.1" 404 12 10.0.0.5 - - [11/Oct/2026:10:00:05 +0000] "GET /api HTTP/1.1" 500 87 10.0.0.7 - - [11/Oct/2026:10:00:06 +0000] "GET / HTTP/1.1" 200 512Solution
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -2$ awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -2 3 10.0.0.5 2 10.0.0.7Only the file name and the
headcount changed.uniq -conly merges lines that are next to each other, so the firstsorthas to group identical IPs before it runs. -
Check the claim that Bash only does integer arithmetic. Divide 7 by 2 in Bash, then do the same division in
awk.Solution
echo "bash: 7 / 2 = $(( 7 / 2 ))" echo "bash: 7 % 2 = $(( 7 % 2 ))" awk 'BEGIN { print "awk: 7 / 2 = " 7 / 2 }'$ bash ints.sh bash: 7 / 2 = 3 bash: 7 % 2 = 1 awk: 7 / 2 = 3.5Bash drops the fraction without warning, and only
%tells you a remainder was there. When you need decimals, hand the arithmetic to a tool that has them. -
Write a script that uses the bash-only
[[ ]]test. Run it withbash, and then withshon a Debian or Ubuntu machine, where/bin/shisdash.Solution
name="web-01" if [[ $name == web-* ]]; then echo "$name is a web server" fi$ bash brackets.sh web-01 is a web server $ sh brackets.sh; echo "exit: $?" brackets.sh: 2: [[: not found exit: 0 $ ls -l /bin/sh lrwxrwxrwx 1 root root 4 Mar 31 2024 /bin/sh -> dashUnder
dash,[[is just an unknown command. Theiftreats that failure as “false”, skips the body, and the script still exits 0. That silent wrong answer is why server scripts should name bash explicitly instead of relying on/bin/sh. -
Adapt the “non-200 status” loop so it reads file paths from
files.txtand alerts on any path that does not exist. This version needs no network.Solution
With
files.txtholding/etc/hostname,/etc/missing.confand/etc/passwd, one per line:while IFS= read -r path; do [[ -e "$path" ]] || echo "ALERT: $path does not exist" done < files.txt$ bash exists.sh ALERT: /etc/missing.conf does not existThe loop is the same; only the check changed. Swapping
curlfor[[ -e ]]turns the URL monitor into a file monitor. Composing small checks into a loop like this is what “glue language” means.
What’s Next
The next tutorial covers getting Bash set up on your system — whether you’re on Linux, macOS, or Windows via WSL — and writing your very first script.