Recommended Free Tools
To write a Bash script, put commands in a text file that starts with #!/usr/bin/env bash, save it, and run it with Bash (or make it executable and run the file). Bash then parses each line, expands variables and patterns, runs commands, and returns a status code. This guide builds a reliable first script and explains the syntax you need for variables, quoting, conditions, loops, functions, pipelines, and error handling.
Your first Bash script
Bash is both a command interpreter and a programming language. The examples below target Bash 5.3, documented in the GNU Bash Reference Manual, Edition 5.3 (updated 18 May 2025): GNU Bash Reference Manual.
- Create a file named
backup-note.sh. - Add this content:
#!/usr/bin/env bash
# Print a note for the selected project.
project="My Documents"
printf 'Preparing: %sn' "$project"
mkdir -p -- "$HOME/archive/$project"
The first line is the shebang. It tells the operating system to find an interpreter named bash through env; it is not a comment when the file is started as a program. The assignment creates a variable without spaces around =. The quoted expansion preserves the space in My Documents. mkdir is an external utility; Bash starts it and records its exit status.
Save and run it
You can always select the interpreter explicitly:
bash backup-note.sh
To run the file directly, give it execute permission and invoke its path:
Do these 3 things before closing this tab:
1Fix the driver behind crashes, sound loss and screen glitches2Clear out junk files and repair common Windows errors3Scan for outdated or missing drivers - takes under a minute#1 Best Overall
chmod u+x backup-note.sh
./backup-note.sh
A filename extension does not make a script executable. Direct execution needs execute permission and a valid shebang; bash backup-note.sh needs read access and uses the Bash binary you named, regardless of the file’s mode.
How Bash processes a command
Bash reads input, divides it into words and operators, parses the command, performs expansions, applies redirections, runs the command, and collects its status. Quoting matters during this process: it controls whether spaces, wildcard characters, variable references, and command substitutions retain their literal meaning.
Single and double quotes
name="Ada Lovelace"
printf '%sn' "$name" # one argument: Ada Lovelace
printf '%sn' '$name' # literal text: $name
printf '%sn' "$HOME" # expands HOME
Double quotes allow parameter and command substitutions while protecting the resulting spaces and wildcard characters. Single quotes preserve everything literally until the next single quote. As a default, quote variable expansions: an unquoted expansion can be split into several words and then treated as a filename pattern.
Rank #2
Variables and command-line arguments
Use $variable or ${variable} to read a value. Braces make boundaries clear:
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →prefix="report"
printf '%sn' "${prefix}_2026.txt"
Arguments supplied after the script name are positional parameters. $0 is the script name, $1 through $9 are individual arguments, "$@" expands to one quoted word per argument, and $# is the argument count.
#!/usr/bin/env bash
if (( $# != 1 )); then
printf 'Usage: %s DIRECTORYn' "$0" >&2
exit 2
fi
printf 'Scanning: %sn' "$1"
Use "$@" when forwarding arguments without losing spaces:
Rank #3
for item in "$@"; do
printf 'Item: %sn' "$item"
done
Tests and conditional logic
An if command runs its body when the test command returns status zero. Bash offers the Bash-specific [[ ... ]] compound conditional, arithmetic evaluation with (( ... )), and the portable [ ... ] (also called test).
path="$1"
if [[ -f "$path" ]]; then
printf 'Regular file: %sn' "$path"
elif [[ -d "$path" ]]; then
printf 'Directory: %sn' "$path"
else
printf 'Not found: %sn' "$path" >&2
exit 1
fi
count=3
if (( count > 0 )); then
printf 'Remaining: %dn' "$count"
fi
Inside [[ ... ]], use operators such as ==, !=, -f (regular file), and -d (directory). Arithmetic conditions use numbers rather than string operators. Quote expansions in ordinary test expressions unless you deliberately need pattern matching.
Loops for repeated work
Iterating over arguments or a known list
for file in "$@"; do
printf 'Would process %sn' "$file"
done
Reading lines safely
while IFS= read -r line; do
printf '> %sn' "$line"
done < input.txt
IFS= prevents trimming leading and trailing whitespace, and read -r leaves backslashes unchanged.
Counting with arithmetic
n=1
while (( n <= 3 )); do
printf '%dn' "$n"
((n++))
done
Functions and Bash arrays
Functions group commands for reuse and run in the current shell context. Unless you explicitly return another status, a function returns the status of its last command.
say_status() {
local label=$1
printf '%s: %sn' "$label" "${2:-unknown}"
}
say_status "Database" "ready"
local limits a variable to the function. Use return for a function status, and use printf or output variables for results.
Indexed arrays are Bash-specific, so these examples require Bash rather than an arbitrary /bin/sh:
Free tools Windows power users keep installed
One-click scans. No signup required.
Best Value
files=("notes.txt" "photo archive.jpg")
for file in "${files[@]}"; do
printf '%sn' "$file"
done
Redirection and pipelines
| Syntax | Effect |
|---|---|
command > file |
Write standard output to a file, replacing it. |
command >> file |
Append standard output. |
command 2> errors.log |
Write standard error separately. |
command &> all.log |
Write standard output and standard error together (Bash syntax). |
command1 | command2 |
Connect the first command’s standard output to the second command’s standard input. |
grep -i 'failed' application.log | sort > failed-sorted.txt
Utilities such as grep, find, and sed are separate programs, and their options can differ between operating systems. Commands in a multi-command pipeline generally run in separate subshells, so a variable changed inside a pipeline may not change the calling shell.
Exit statuses and dependable failure handling
By convention, status 0 means success and a nonzero status means failure. $? contains the status of the most recently completed command, so capture or test it immediately:
if ! mkdir -p -- "$target"; then
printf 'Cannot create %sn' "$target" >&2
exit 1
fi
A missing command normally returns status 127; a command that was found but cannot be executed returns 126, according to the Bash manual.
Pipeline status and pipefail
| Setting | Pipeline result |
|---|---|
| Default | The status of the last command in the pipeline. |
set -o pipefail |
The status of the rightmost command that exits nonzero, or zero when every command succeeds. |
set -o pipefail
if ! grep -i 'failed' application.log | sort > failed-sorted.txt; then
printf 'The pipeline failedn' >&2
exit 1
fi
What set -e does—and does not do
set -e asks Bash to exit when a simple command returns nonzero, but it is not a universal error-trapping switch. Bash suppresses that exit behavior in documented contexts, including commands used as tests in if, commands in parts of && and || lists (with context-dependent exceptions), and non-final pipeline commands; pipefail changes pipeline status but does not remove every exception. For important operations, write an explicit check such as if ! command; then ... fi. If you use strict-mode combinations, understand each option and test the exact control flow rather than assuming every failure stops the script.
Bash versus POSIX sh
| Choice | Use when | Examples |
|---|---|---|
| Bash script | You control the interpreter and want Bash features. | #!/usr/bin/env bash, [[ ... ]], arrays, pipefail. |
| POSIX shell script | The script must run under different /bin/sh implementations. |
Use POSIX syntax and avoid Bash-only constructs. |
/bin/sh is not guaranteed to be Bash. A script using arrays, [[ ... ]], or other Bash-specific syntax should name Bash in its shebang and be started with Bash. If portability is the priority, write to the POSIX shell language instead and test on every target shell.
A practical checklist before sharing a script
- Start with the intended interpreter, such as
#!/usr/bin/env bash. - Quote expansions unless you intentionally need splitting or pattern expansion.
- Validate required arguments and quote path arguments, including those beginning with
-when a utility supports--. - Check statuses for operations whose failure matters; decide whether
pipefailmatches your pipeline requirements. - Keep Bash-only syntax out of scripts that claim POSIX
shportability. - Test with filenames containing spaces, empty input, missing files, and commands that fail.
Further reference
The complete language reference, including expansions, redirections, builtins, and option details, is available in the GNU Bash Reference Manual. GNU also provides a Bash manual landing page with information about the manual and printed editions.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

