Skip to main content
Bash · lesson 1 of 22
Bash beginner 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.

TermWhat it actually is
TerminalThe window/application (iTerm2, GNOME Terminal, Windows Terminal). It handles rendering text and capturing keyboard input.
ShellThe interpreter running inside the terminal. It reads commands and executes them. Examples: bash, zsh, fish, dash.
BashOne 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.

ShellNotes
bashDefault on Linux, available everywhere, POSIX-compatible plus extras
zshDefault on macOS since Catalina; better interactive features, compatible with most bash scripts
fishUser-friendly, great autocomplete, but intentionally not POSIX-compatible
dashMinimal, fast, strict POSIX — used as /bin/sh on Debian/Ubuntu for boot scripts
kshKorn 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

  1. Put the three commands from “Your First Bash Command” into a file named first.sh, run it with bash, 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_VERSION variable:

    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)-release

    bash --version starts a second bash process just to ask it. $BASH_VERSION is the version of the shell that is already running your script. Here they match because there is only one bash installed.

  2. 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 512
    Solution
    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.7

    Only the file name and the head count changed. uniq -c only merges lines that are next to each other, so the first sort has to group identical IPs before it runs.

  3. 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.5

    Bash 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.

  4. Write a script that uses the bash-only [[ ]] test. Run it with bash, and then with sh on a Debian or Ubuntu machine, where /bin/sh is dash.

    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 -> dash

    Under dash, [[ is just an unknown command. The if treats 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.

  5. Adapt the “non-200 status” loop so it reads file paths from files.txt and alerts on any path that does not exist. This version needs no network.

    Solution

    With files.txt holding /etc/hostname, /etc/missing.conf and /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 exist

    The loop is the same; only the check changed. Swapping curl for [[ -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.

Frequently Asked Questions

Is Bash still relevant in 2025?
Absolutely. Bash is the default shell on nearly every Linux server and macOS machine. CI/CD pipelines, Docker entrypoints, Kubernetes init containers, and cloud bootstrap scripts are overwhelmingly written in Bash.
Should I learn Bash or Python for automation?
Learn both. Bash excels at gluing Unix tools together, file manipulation, and tasks where spawning processes is the main work. Python wins when you need data structures, HTTP calls, or logic that would be painful in Bash.
What is the difference between sh and bash?
sh is the POSIX-standard shell. Bash (Bourne Again SHell) is a superset of sh with extra features like arrays, [[ ]], and process substitution. Most scripts use bash explicitly via the shebang #!/usr/bin/env bash.