Skip to content

Your First Bash Script

Part of Essentials — Bash Scripting

First in the Essentials Bash series. Assumes you're comfortable with the terminal — if you need a refresher, start with Command Line Fundamentals. Next up: Variables and Quoting.

You've been running the same sequences of commands manually. A Bash script captures that sequence, makes it repeatable, and lets you hand it to a colleague or a CI pipeline. The gap between "I know the commands" and "I wrote the script" is smaller than it looks.


Where You've Seen This

If you've typed the same sequence of commands more than once, you've already felt the problem a script solves — it's just that sequence saved to a file and made repeatable. If you've used Windows batch files (.bat), the concept is identical; Linux just uses different syntax and bash is available everywhere by default.


A First Script

system-info.sh
1
2
3
4
5
6
7
8
9
#!/usr/bin/env bash

echo "=== System Information ==="
echo "User:     $(whoami)"
echo "Host:     $(hostname)"
echo "Date:     $(date)"
echo "Uptime:   $(uptime -p)"
echo "Disk:     $(df -h / | awk 'NR==2 {print $3 " used of " $2}')"
echo "Memory:   $(free -h | awk '/^Mem:/ {print $3 " used of " $2}')"

Three things to notice:

  • $(...) runs a command and substitutes its output inline — covered in depth in Variables and Quoting
  • echo sends output to stdout; pipelines and redirection work just like at the prompt
  • There's no main() function — Bash runs top to bottom

The Shebang Line

That first line — #!/usr/bin/env bash — tells the OS which interpreter to run the script with. Without it, the shell has to guess, and it can guess wrong.

Uses env to find bash in $PATH. Survives if bash is installed in a non-standard location — common on macOS, BSD, or NixOS. The portable choice.

Portable Shebang
#!/usr/bin/env bash

Absolute path to bash on most Linux systems. Slightly faster (skips the env lookup). Fine for scripts you know will only run on standard Linux.

Direct Path Shebang
#!/bin/bash

POSIX shell — not Bash. More portable across Unix systems but loses Bash-specific features like [[ ]], arrays, and process substitution. Only use this if you genuinely need POSIX portability.

POSIX Shell Shebang
#!/bin/sh

The shell that called the script runs it. If your user's shell is zsh, the script runs as zsh. Unpredictable in automated contexts. Don't do this for scripts you'll share or schedule.

The rule of thumb: Use #!/usr/bin/env bash unless you have a specific reason to do otherwise.


Making It Executable

A new file isn't executable by default. Two steps to run it:

Make a Script Executable
chmod +x system-info.sh
./system-info.sh

The ./ prefix tells the shell "look here, not in PATH." Without it, the shell searches PATH and won't find your script unless you've added its directory.

Read scripts before running them

chmod +x and ./run.sh is a common pattern in installation instructions. Read the script before executing it — it runs with your permissions.

See File Permissions for the full explanation of chmod and permission bits.

Running Without chmod +x

You can also run a script by passing it directly to bash:

Run Without Executable Bit
bash system-info.sh

This works even without chmod +x. Useful for quick tests, but the executable bit is the right approach for scripts you'll deploy or share.


Where to Store Scripts

Where a script lives determines who can run it and how easily:

For scripts only you need to run, install them to ~/bin and add it to your PATH:

User-Scoped Scripts in ~/bin
1
2
3
mkdir -p ~/bin
cp system-info.sh ~/bin/system-info
chmod +x ~/bin/system-info

Add ~/bin to your PATH in ~/.bashrc:

Add ~/bin to PATH
export PATH="$HOME/bin:$PATH"

Now you can run system-info from anywhere without ./.

For scripts that all users on the system should access:

System-Wide Installation
sudo cp system-info.sh /usr/local/bin/system-info
sudo chmod +x /usr/local/bin/system-info

/usr/local/bin/ is already in PATH for all users. Scripts here survive package manager updates (unlike /usr/bin/).

For shared tooling in a team environment:

Company Script Directory
1
2
3
sudo mkdir -p /opt/company/bin
sudo cp system-info.sh /opt/company/bin/system-info
sudo chmod +x /opt/company/bin/system-info

Add /opt/company/bin to PATH system-wide via /etc/profile.d/company.sh. Putting shared scripts under /opt/company/ keeps them out of package-managed paths and makes them easy to find.


Practice Exercises

Exercise 1: Write a Health Check Script

Write a script called health-check.sh that prints the following in a formatted block:

  • Current user and hostname
  • Load average (use uptime)
  • Disk usage on / (use df -h)
  • Whether a specific process is running (use pgrep -x sshd)
Solution
health-check.sh
#!/usr/bin/env bash

echo "=== Health Check: $(hostname) ==="
echo "User:    $(whoami)"
echo "Load:    $(uptime | awk -F'load average:' '{print $2}')"
echo "Disk:    $(df -h / | awk 'NR==2 {print $5 " used (" $3 "/" $2 ")"}')"

if pgrep -x sshd &>/dev/null; then
    echo "sshd:    running"
else
    echo "sshd:    NOT RUNNING"
fi
Exercise 2: Script Location Practice

Create a script called whoami-full.sh that prints your username, your primary group, and your home directory. Install it as a personal tool so you can run whoami-full from any directory without ./.

Solution
whoami-full.sh
1
2
3
4
5
#!/usr/bin/env bash

echo "User:  $(whoami)"
echo "Group: $(id -gn)"
echo "Home:  ${HOME}"

Install it:

Install to ~/bin
1
2
3
4
5
6
mkdir -p ~/bin
cp whoami-full.sh ~/bin/whoami-full
chmod +x ~/bin/whoami-full
# Ensure ~/bin is in PATH — add to ~/.bashrc if not already there:
# export PATH="$HOME/bin:$PATH"
whoami-full

Quick Recap

  • The shebang line (#!/usr/bin/env bash) tells Linux which interpreter to use — always include it
  • chmod +x script.sh makes the file executable; ./script.sh runs it
  • bash script.sh runs a script without the executable bit — useful for one-off testing
  • ~/bin/ for personal scripts, /usr/local/bin/ for system-wide, /opt/company/bin/ for team tooling
  • Add the directory to $PATH so you can call scripts by name from anywhere

Further Reading

Command References

  • man bash — the full Bash reference; the "INVOCATION" section covers how scripts are started
  • man chmod — file permission bit reference

Deep Dives

Official Documentation

Exploring Python

  • Why Python (Not Just Bash) — When your script grows into something bigger: the signals that mean Python is the right next step

What's Next?

Head to Variables and Quoting — how to declare variables, why quoting prevents catastrophic bugs, and how command substitution captures runtime values.