Bash Setup and First Script
Install and configure Bash on Linux, macOS, and Windows WSL, then write and run your first shell script.
Bash on Linux
Bash is pre-installed on all major Linux distributions, which is one of the reasons it is the default choice for server scripting. You rarely need to install it, but it’s worth verifying which version you have since some features like associative arrays and the mapfile command require Bash 4 or later.
bash --version
# GNU bash, version 5.2.15(1)-release
# Check your default shell
echo $SHELL
# /bin/bash
# List available shells
cat /etc/shells
To upgrade bash on Ubuntu/Debian:
sudo apt update && sudo apt install bash
On Fedora/RHEL:
sudo dnf install bash
Bash on macOS
macOS ships with bash 3.2 (from 2007, due to licensing). For scripting you want bash 5, which adds associative arrays, improved globbing, and many other features that modern scripts rely on.
# Install via Homebrew
brew install bash
# Add to allowed shells
echo "$(brew --prefix)/bin/bash" | sudo tee -a /etc/shells
# Change your default shell (optional — zsh is fine for interactive use)
chsh -s "$(brew --prefix)/bin/bash"
Always use #!/usr/bin/env bash in your shebang — it finds the first bash in $PATH, picking up the Homebrew version on macOS rather than the system’s outdated 3.2.
Bash on Windows via WSL
WSL 2 gives you a real Linux kernel on Windows, which is the recommended way to run Bash scripts on Windows for development. It eliminates the path and line-ending issues that come with trying to run Linux scripts natively on Windows.
# In PowerShell (as Administrator)
wsl --install
# Installs Ubuntu by default, includes bash
# After reboot and Ubuntu setup:
wsl
# You're now in a bash shell
bash --version
Alternatively, Git Bash (part of Git for Windows) provides bash for simple scripting without a full Linux environment. It is good enough for most day-to-day use but lacks some Linux utilities.
The Shebang Line
The shebang (#!) is the first line of every proper bash script. It tells the OS which interpreter to use when the file is run directly. Without it, the kernel does not know whether the file is a bash script, a Python script, or something else entirely.
#!/usr/bin/env bash
Why env bash instead of /bin/bash?
/bin/bashis hardcoded — if bash is somewhere else (like/usr/local/bin/bashon macOS with Homebrew), the script fails silently or with a confusing error./usr/bin/env bashsearches$PATHfor bash, making scripts portable across systems and picking up the right version automatically.
#!/usr/bin/env bash
# Good: portable, finds Homebrew bash on macOS
#!/bin/bash
# Acceptable on Linux where bash is always at /bin/bash
#!/bin/sh
# Use only if you need strict POSIX compatibility (no bash-specific features)
Making a Script Executable
There are two ways to run a bash script, and understanding the difference matters when you are debugging permission errors or writing CI steps.
# Method 1: Pass it to the bash interpreter explicitly (no shebang needed)
bash myscript.sh
# Method 2: Execute it directly (requires shebang + execute permission)
chmod +x myscript.sh
./myscript.sh
chmod +x adds the execute bit. Check permissions with ls -l:
ls -l myscript.sh
# -rwxr-xr-x 1 user group 42 Jan 1 10:00 myscript.sh
# ^^^
# rwx = owner can read, write, execute
Your First Script
Writing your first script reinforces the core concepts all at once: the shebang, the safe header, variables, arguments, and command substitution. Create a file called hello.sh:
#!/usr/bin/env bash
# hello.sh — my first bash script
# This demonstrates the shebang, safe error handling, variables, and arguments.
set -euo pipefail # exit on error, undefined vars, and pipeline failures
NAME="${1:-World}" # use first argument, or "World" if none provided
echo "Hello, $NAME!"
echo "Today is $(date '+%A, %B %d %Y')"
echo "You are running bash $(bash --version | head -1 | awk '{print $4}')"
Run it:
chmod +x hello.sh
./hello.sh
# Hello, World!
# Today is Wednesday, January 01 2025
# You are running bash 5.2.15(1)-release
./hello.sh Alice
# Hello, Alice!
Script File Organization
A well-structured script is easier to read, debug, and hand off to teammates. The pattern below separates constants, utility functions, and logic into clearly labelled sections.
#!/usr/bin/env bash
# =============================================================================
# script-name.sh
# Description: What this script does in one sentence.
# Usage: ./script-name.sh [options] <argument>
# Author: Your Name
# =============================================================================
set -euo pipefail
# --- Constants ---------------------------------------------------------------
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="/tmp/script-name.log"
# --- Functions ---------------------------------------------------------------
log() {
# Writes a timestamped message to both the terminal and the log file
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
usage() {
echo "Usage: $0 [options] <argument>"
echo " -h Show this help"
exit 0
}
# --- Main --------------------------------------------------------------------
main() {
log "Script started"
# ... your logic here
log "Script finished"
}
main "$@"
Using a Text Editor
For scripts, any editor works. On a server you’ll often use:
# nano — easiest for beginners
nano myscript.sh
# vim — steep learning curve, available everywhere
vim myscript.sh
# VS Code via WSL or remote SSH
code myscript.sh
For serious script development, VS Code with the shellcheck extension is the best setup — it catches common bash mistakes in real time as you type.
Installing ShellCheck
ShellCheck is a static analysis tool for bash scripts. It’s like ESLint but for bash — it catches quoting bugs, uninitialized variables, command injection risks, and dozens of other issues before you run the script. Install it early and run it on every script you write.
# Ubuntu/Debian
sudo apt install shellcheck
# macOS
brew install shellcheck
# Run it
shellcheck myscript.sh
# Warnings with line numbers and explanations
A clean ShellCheck run means your script avoids the most common pitfalls. Many teams enforce shellcheck in CI alongside their linters for other languages.
What’s Next
With Bash set up and your first script running, the next step is learning about variables — how to store, reference, and manipulate data in bash scripts.