Writing Your First Bash Script
A Bash script is simply a text file containing a sequence of shell commands that Bash executes in order, exactly as if you had typed them interactively. Scripting turns repetitive manual work — backups, deployments, log rotation, environment setup — into a single reliable, version-controllable command. The barrier to writing your first script is low: you need a text file, a shebang line telling the kernel which interpreter to use, executable permission, and a location the shell can find. From there, everything you already know about running commands at the prompt applies inside the script.
Cricket analogy: A training script is simply a written sequence of drills a coach runs through in order, exactly as if calling them out live; turning repetitive nets sessions into one documented routine needs a written plan, a header naming the coach in charge, sign-off to run it, and a spot in the team's training folder.
The shebang and how scripts are executed
The first line of a script, #!/bin/bash (or the more portable #!/usr/bin/env bash), is called the shebang. The kernel reads these first two bytes (#!) and uses the rest of the line to determine which interpreter should run the file — this is how ./script.sh knows to invoke Bash rather than trying to execute the text as a binary. #!/usr/bin/env bash is preferred in portable scripts because it locates bash via the user's PATH rather than assuming it lives at /bin/bash, which matters on systems (like some macOS setups or containers) where Bash is installed elsewhere. If you omit the shebang and instead run the script as bash script.sh, the shebang line is ignored and treated as a comment.
Cricket analogy: The team sheet's header line names the format being played (Test vs T20), and officials read just that header to know which rulebook applies before the toss; naming the format generically as 'limited overs' (like #!/usr/bin/env bash) is more portable across grounds than hardcoding one ground's rules, and if you skip the header, the format assumption is ignored entirely.
#!/usr/bin/env bash
#
# backup-home.sh — archive the current user's home directory
# Usage: ./backup-home.sh [destination-dir]
set -euo pipefail # exit on error, unset variable, or failed pipeline stage
dest="${1:-/var/backups}"
timestamp="$(date +%Y%m%d-%H%M%S)"
archive="${dest}/home-backup-${timestamp}.tar.gz"
mkdir -p "$dest"
tar -czf "$archive" -C "$HOME" .
echo "Backup written to $archive"
ls -lh "$archive"Making a script executable and running it
Newly created scripts are not executable by default; chmod +x script.sh adds the executable bit for the owner (and typically group/other depending on umask). Once executable, ./script.sh runs it from the current directory — the leading ./ is required unless the script's directory is in your PATH, because Bash does not search the current directory for commands by default (a deliberate security measure). Alternatively, bash script.sh or sh script.sh runs it by explicitly invoking an interpreter, bypassing both the shebang and the need for the executable bit, though sh may interpret Bash-specific syntax differently.
Cricket analogy: A newly registered player isn't automatically cleared to play (chmod +x) — the board must certify them first; even then, you must name them explicitly on the team sheet (./script.sh) rather than assume selectors will notice by default, since committees don't scan the crowd for unlisted talent; alternatively, a wildcard entry (bash script.sh) bypasses certification but may be interpreted differently under a different format's rules.
chmod +x backup-home.sh
./backup-home.sh /mnt/backups
# Or run without the executable bit at all
bash backup-home.sh /mnt/backups
# Install it on PATH so it can be called by name anywhere
sudo cp backup-home.sh /usr/local/bin/backup-home
sudo chmod +x /usr/local/bin/backup-home
backup-homeset -euo pipefail is often called Bash's 'strict mode.' -e exits immediately if any command returns non-zero, -u treats referencing an unset variable as an error rather than silently substituting an empty string, and -o pipefail makes a pipeline fail if any stage fails, not just the last one. Together they catch classes of bugs — typoed variable names, silently swallowed errors — that make ad-hoc scripts unreliable in production.
set -e does not catch every failure: it is bypassed inside if conditions, inside &&/|| chains, and for commands whose exit status is checked directly. A common surprise is that a failing command inside a pipeline still lets the script continue unless pipefail is also set, since -e alone only inspects the pipeline's overall (last-command) exit status.
Structure, comments, and style
Good scripts start with a comment block describing purpose and usage, use set -euo pipefail near the top, quote variables, and prefer functions for anything reused more than once. Comments begin with # and run to the end of the line; there is no native multi-line comment syntax, though a : <<'END' here-document is sometimes used as a workaround. Keeping scripts under version control (even a small personal Git repo of ~/.local/bin scripts) is standard practice once you have more than a handful.
Cricket analogy: A well-prepared team keeps a written game plan header explaining strategy, sets a 'stop immediately on any collapse' rule (set -euo pipefail) rather than playing on through disaster, names players precisely to avoid mix-ups, and reuses a standard fielding drill (a function) rather than re-explaining it each time; notes use a quick marginal comment (#), and the game plan is archived season over season like a version-controlled playbook.
- A script's first line, the shebang (
#!/usr/bin/env bash), tells the kernel which interpreter to execute it with. chmod +xgrants execute permission;./script.shruns it from the current directory since PATH excludes.by default.set -euo pipefailis standard 'strict mode' that fails fast on errors, unset variables, and failed pipeline stages.- Running a script as
bash script.shbypasses the shebang and the need for the executable bit. - Comments start with
#; there is no built-in multi-line comment, though a here-doc trick can approximate one. - Scripts placed on your PATH (e.g.
/usr/local/bin) can be invoked by name from anywhere on the system.
Practice what you learned
1. What is the purpose of the shebang line `#!/usr/bin/env bash`?
2. Why does `./script.sh` require the leading `./` even after the file is executable?
3. What does the `-u` option in `set -euo pipefail` do?
4. Which command grants a script file execute permission for its owner?
5. What happens if you run `bash script.sh` on a script that has a shebang line?
Was this page helpful?
You May Also Like
Variables and Quoting in Bash
Understand how Bash stores and expands variables, and why quoting rules — single, double, and unquoted — are critical to writing scripts that don't break on real-world input.
Conditionals in Bash (if, case)
Master Bash's conditional constructs — if/elif/else, test expressions, [[ ]] versus [ ], and case statements — to make scripts branch correctly on real-world conditions.
Exit Codes and Error Handling
Understand how every command reports success or failure through its exit status, and how to build scripts that detect and respond to errors reliably.
Command-Line Arguments and getopts
Learn how Bash scripts receive positional parameters, how to shift and iterate over them, and how to build proper option parsing with getopts.
Related Reading
Related Study Notes in DevOps
Browse all study notesNginx Study Notes
DevOps · 30 topics
DevOpsAnsible Study Notes
DevOps · 30 topics
DevOpsAdvanced Kubernetes Study Notes
Kubernetes · 30 topics
DevOpsAdvanced Bash Scripting Study Notes
Bash · 30 topics
DevOpsApache Kafka Study Notes
Kafka · 30 topics
DevOpsDocker & Kubernetes Study Notes
YAML · 40 topics