Bash Scripting for Engineers›01 · Scripts, variables & quoting

Lesson 01 of 8 · Modules

Scripts, variables & quoting

Write your first real scripts: the shebang, running them, variables, the three kinds of quoting, command substitution and exit codes, plus the quoting rule that prevents most Bash bugs.

Beginner
Key wordsshebangchmod +xvariablesquotingcommand substitutionexit status$?

Why Bash still matters

Every Linux server has Bash. Installers, container entrypoints, CI steps, cron jobs and "just automate this" tasks are very often Bash scripts. Writing them safely is a core skill, even if you later move bigger tools to Python.

A script is a recipe card for the computer. Instead of telling it each step by hand every morning, you write the steps down once, and the computer follows the card whenever you ask, in exactly the same way every time.

Your first script

#!/usr/bin/env bash
# hello.sh: my first script
name="Asha"
echo "Hello, $name. Today is $(date +%A)."
$ chmod +x hello.sh
$ ./hello.sh
Hello, Asha. Today is Saturday.
  • #!/usr/bin/env bash (the shebang) says which interpreter runs the file.
  • chmod +x makes it executable; ./ runs it from the current directory.
  • bash -x hello.sh prints each command before running it. It's the easiest debugger there is.

Variables

name="Asha"            # no spaces around =
count=3
greeting="Hi $name"    # expands to: Hi Asha
echo "$greeting, you have $count messages"
echo "${name}_backup"  # braces separate the name from following text → Asha_backup

Environment variables ($HOME, $PATH, $USER) are just variables your script inherits. export VAR=value passes a variable on to programs your script starts.

The one rule: quote your variables

Bash splits unquoted variables on spaces and expands wildcards in them. That causes most Bash bugs:

$ file="my report.txt"
$ ls $file
ls: cannot access 'my': No such file or directory
ls: cannot access 'report.txt': No such file or directory
$ ls "$file"
my report.txt
Quoting Variables expand? Word splitting / globbing? Use for
"double" ✅ ❌ Almost everything
'single' ❌ ❌ Literal text, regexes, JSON, awk programs
none ✅ ✅ (dangerous) Rarely, deliberately

Quote by default

Write "$var", "$@" and "$(command)" unless you have a specific reason not to. shellcheck (lesson 07) flags every place you forgot.

Capturing output: command substitution

today=$(date +%F)                     # 2026-09-27
kernel=$(uname -r)
files=$(ls /etc | wc -l)
echo "On $today, kernel $kernel, /etc has $files entries"

Use $(...), not the old backticks. It nests cleanly and is easier to read.

Exit status: did it work?

Every command returns an exit status: 0 = success, anything else = failure.

$ grep -q root /etc/passwd; echo $?
0
$ grep -q nobody-here /etc/passwd; echo $?
1
$ ls /nope; echo $?
ls: cannot access '/nope': No such file or directory
2

Chain commands on success or failure:

mkdir -p /tmp/work && cd /tmp/work       # cd only if mkdir succeeded
ping -c1 -W1 10.0.0.1 || echo "gateway unreachable"

Your script's own exit status is the last command's, or whatever you pass to exit:

exit 0     # success
exit 1     # failure

Try it: a system summary script

Write summary.sh that prints:

  1. The hostname and today's date.
  2. The number of logged-in users (who | wc -l).
  3. Free space on / (df -h / | tail -1).
  4. Whether nginx is running: use systemctl is-active --quiet nginx with && and || to print "nginx: up" or "nginx: down".

Run it with bash -x summary.sh to see each step. Then create a file named my report.txt and write a line that wc -ls it through a variable. Make it fail without quotes, then fix it.

Going deeper: small things that bite later

  • #!/usr/bin/env bash finds bash on PATH (portable across systems). #!/bin/sh means POSIX sh, which on Debian/Ubuntu is dash and lacks many Bash features.
  • local variables in functions (lesson 03) avoid clobbering globals.
  • readonly CONFIG=/etc/app.conf protects constants.
  • Prefer printf '%s\n' "$var" over echo for arbitrary data: echo treats some values (like -n) as options.

Recap

  • Shebang + chmod +x; debug with bash -x.
  • name=value with no spaces; use as "$name" or "${name}".
  • Quote your variables; single quotes for literal text.
  • $(command) captures output; $? and exit carry exit status (0 = success); chain with && and ||.

This site is a public version of my personal engineering knowledge hub. It intentionally excludes confidential company information and internal operational details.