Bash Script Basics
The "13. Disk, Filesystem, LVM, and Storage" module made one important observation in Disk-Full Troubleshooting: the df -h → df -i → du → lsof sequence was a precise, ordered check that had to be repeated by hand every time. Any task — a check, a backup, a cleanup — that gets repeated by hand more than a few times is a natural candidate for automation. Bash scripting is exactly that automation tool. This article opens the "14. Bash Scripting" module, starting with what a script is, its structure, and how it's run; later articles add variables, conditionals, loops, functions, and error handling, and the module ends with three complete, working production scripts.
What You Will Learn
By the end of this article you will be able to:
- explain what a script is and why it's worth writing one;
- explain the shebang line's purpose and how to choose it;
- grant a script execute permission and run it in two different ways;
- follow commenting conventions;
- start checking a script with
shellcheck.
What a Script Is and Why It's Needed
A script is a set of commands, written into a text file, executed in sequence. Any command you'd type one at a time into a terminal can be placed inside a script; the difference is that a script is written once and then rerun any number of times, in exactly the same order, without mistakes. A script is executed by an interpreter — a program that reads the commands one by one and hands them to the system. For Bash scripts, that interpreter is the bash program itself.
A script earns its keep in situations like these:
- the same sequence of commands needs to run regularly (daily, weekly);
- a command line is long enough that retyping it by hand invites mistakes;
- a task needs to run unattended, through
cronor a systemd timer; - the same action needs to be applied to multiple servers.
A script isn't a programming language — it's a shell that organizes existing commands. For heavy computation or processing large amounts of data, a full programming language like Python is the usual choice; but a large share of a system administrator's day-to-day work — managing files, processes, and commands — is solved at exactly the level of a Bash script.
Your First Script
Using a plain text editor (nano, vim), create a file named hello.sh:
Making the file executable and running it:
chmod +x adds the execute permission to the file — without it, the system won't allow the file to run as a script, even if the text itself is written correctly. The full logic of permissions was covered in File Permissions; what matters for a script is that the owner (or the relevant group/others category) needs the x bit.
Shebang: Choosing the Interpreter
A script's first line — #!/bin/bash — is called the shebang (# — "sharp," ! — "bang," shortened together to "shebang"). This line tells the system which program should execute the commands in the file. When a script is run as ./hello.sh, the system actually runs the program named in the shebang and passes the script's path to it as an argument — meaning the command actually executed looks like /bin/bash ./hello.sh. This can be checked in the process table:
If a shebang script is run instead of sleep, ps -ef output shows a line in exactly the form /bin/bash /path/script.sh — proving what the shebang does.
Two forms exist:
The first points to bash's exact path and works correctly on most Linux distributions. The second finds bash through PATH via the env program; it's used when more portability is needed (for example, when bash might be installed at a different path on different systems). In this course, and on production Linux servers, bash is almost always located at /bin/bash, so the #!/bin/bash form is used.
A shebang isn't specific to bash — it can point to any interpreter:
If no shebang is written at all, the script runs through the current interactive shell — this can sometimes seem to work, but it's unreliable: different users can have different default shells (bash, zsh, dash), and their syntax isn't identical. This is why writing an explicit shebang is always good practice.
Note
On some distributions (Debian/Ubuntu, for example), /bin/sh actually points to dash, not bash — dash is a POSIX shell that doesn't support many of Bash's convenient extensions (arrays, [[ ]], local, and others). This is why a script using Bash-specific syntax must have exactly #!/bin/bash as its shebang, not #!/bin/sh.
Two Ways to Run a Script
1. With a relative or absolute path:
This works even if the script's directory isn't in the PATH variable, since the path is given explicitly. The ./ marker means the current directory — it can't be dropped, or the shell searches for the script in PATH and usually can't find it.
2. By name alone (if the script is in PATH):
This only works if the script's directory has been added to the $PATH variable. The PATH mechanism covered in Aliases and Environment Variables works exactly the same way here — the shell searches for the command name in the directories listed in PATH.
A script can also be run by calling the interpreter directly, in which case execute permission (the x bit) isn't required, since bash reads the file itself:
This is convenient for a quick check, but for a script used regularly, chmod +x and keeping it as a standalone executable file is recommended.
Comments
Everything after a # character (except the shebang, since it's interpreted as a special first line) is ignored by the interpreter — this is a comment:
#!/bin/bash
# This whole line is a comment
echo "Hello" # The right-hand side of this line is also a comment
Comments are for the person reading the code, not the code itself. A good script's comment explains why something is written a particular way (not what it does — the command itself already shows that) — for example, the reason for a nonstandard workaround or a non-obvious constraint.
Checking a Script: shellcheck
As a script grows longer, small mistakes (a misspelled variable name, a forgotten quote) can go unnoticed. shellcheck is a static analysis tool for Bash scripts that catches common mistakes at the writing stage:
In hello.sh line 4:
echo "Hello, Linux!"
^-- SC2148 (error): Tips depend on target shell and yours is unknown. Add a shebang.
(This example deliberately removes the shebang to show shellcheck warning about it.) Running every script written throughout this module through shellcheck is recommended — it's especially useful once variables and conditionals show up in later articles.
Debugging a Script: bash -x and set -x
shellcheck catches mistakes before a script runs; -x (xtrace) helps when a script runs but does the wrong thing and you can't see why. It prints each command after all variable and command substitution, right before it executes — so you see the actual values Bash used, not what you assumed.
Run the whole script in trace mode without editing it:
Each traced line is prefixed with +. To trace only a suspect section rather than the whole script, wrap it with set -x/set +x:
#!/bin/bash
name="Linux"
set -x # tracing on
greeting="Hello, ${name}!"
set +x # tracing off
echo "$greeting"
This is the fastest way to answer the classic interview question "a script isn't behaving as expected — how do you debug it?": shellcheck for static mistakes, then bash -x (or a scoped set -x) to watch the real, expanded commands as they run.
Tip
Combine it with the strict flags from Exit Codes and Error Handling: running a script as bash -xeuo pipefail script.sh traces every command and stops at the first error and the first unset variable — the single most useful one-line debugging invocation.
Common Mistakes
Execute Permission Not Granted
Cause — the file lacks the x bit. Fix: chmod +x hello.sh.
Forgetting ./
The current directory usually isn't in PATH, so the shell can't find the script. Fix: write ./hello.sh, or give the absolute path.
A Space or Wrong Character in the Shebang Line
Leaving a space between # and ! turns the line into an ordinary comment, and the shebang stops working — the script runs through the current shell instead, which can lead to unexpected behavior.
Line-Ending Characters (CRLF) from a File Edited on Windows
If a script is edited on Windows and then copied to a Linux server, the following error can show up:
The cause — Windows marks line endings with \r\n (CRLF), while Linux uses only \n; the extra \r character gets appended to the shebang line and breaks the path. Checking and fixing it:
Exercises
- Write a script named
whoami.shthat prints the output ofwhoami,hostname, anddatein sequence. Make it executable and run it both ways (./whoami.shandbash whoami.sh). - Deliberately write the shebang line as
# !/bin/bash(with a space in the middle) and observe what changes. - Check the script from exercise 1 with
shellcheckand analyze the output.
Summary
A Bash script combines a sequence of commands into a single executable file. The shebang line determines which interpreter is used, and chmod +x grants the file execute permission — these two are the foundation of any script. The next article, Variables and Arguments, covers how to store data inside a script and pass it in from outside (via the command line) — turning a script from a static list of commands into a flexible tool.