Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Bash scripts become reliable when you understand how Bash turns text into commands and arguments—not just which keywords to type. This tutorial uses Bash 5.3 as its reference scope, teaches the core scripting workflow from commands through error handling, and marks Bash-specific syntax so you can choose the right interpreter for your environment.

What Bash is—and what this tutorial assumes

The GNU Project describes Bash as “the shell, or command language interpreter, for the GNU operating system.” Its name is a pun on “Bourne-Again SHell.” Bash is largely compatible with sh and incorporates useful features associated with other shells, but Bash features are not automatically portable POSIX shell syntax. GNU says Bash is intended to conform to the POSIX Shell and Utilities specification while adding features for interactive use and programming.

Examples here target Bash, with the GNU Bash Reference Manual, Edition 5.3, as the behavior reference. The manual was last updated 18 May 2025. A script that uses Bash arrays or other Bash extensions needs Bash; do not label it as portable sh simply because it runs in a terminal.

To check the Bash version available in your current environment, run bash --version. The first output line identifies the version. If you rely on syntax added in a particular release, check the manual for that version and confirm the target machines provide it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Start with commands, arguments, and exit status

A shell command is made up of a command name and the arguments passed to it. For example, in printf '%sn' 'hello world', printf is the command, while the remaining words are its arguments. Quoting keeps hello world together as one argument.

Commands also return an exit status: by convention, zero indicates success and a nonzero value indicates failure. In Bash, $? contains the status of the most recently completed foreground command. Check it immediately if you need to inspect that result:

mkdir -p reports
status=$?
printf 'mkdir returned %sn' "$status"

In real scripts, it is usually clearer to put a command directly in an if condition than to save and inspect $? afterward; that pattern appears in the error-handling section.

Write and run your first Bash script

A script is a text file containing shell commands. The shebang on its first line names the interpreter to use when the file is executed directly. This example explicitly targets Bash through the common /usr/bin/env lookup:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#!/usr/bin/env bash
printf 'Hello, %s!n' "${1:-there}"
  1. Save the text as hello.sh.

  2. Run it with Bash explicitly: bash hello.sh Mira. This does not require the file to be executable.

  3. To execute it directly, make it executable with chmod +x hello.sh, then run ./hello.sh Mira. The shebang selects the interpreter.

The expression ${1:-there} uses the first positional parameter when it is set and non-empty; otherwise it uses there. The quotes make the resulting value one argument to printf.

Understand words, quoting, and expansion

Bash processes commands through several stages, including expansions and word splitting. A practical consequence: an unquoted variable expansion can turn one intended value into multiple arguments, and wildcard characters can expand to matching filenames. ShellCheck explains these risks in its SC2086 guidance.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Single quotes: keep text literal

Single quotes preserve the characters inside them literally. They are useful when you want text such as $HOME or * treated as text rather than expanded by Bash.

printf '%sn' 'Literal text: $HOME and *'

Double quotes: expand variables as one argument

Double quotes allow parameter and command substitutions, while preserving the result as one argument. Quote variable expansions by default when the value represents one argument:

name='Ada Lovelace'
printf 'User: %sn' "$name"

Without the quotes around $name, Bash may split the value at spaces and expand wildcard characters in it. That can change both the number and contents of the arguments a command receives.

Parameter expansion: use values and defaults

Parameter expansion substitutes a variable or positional parameter. Braces make the boundary explicit, especially when adding adjacent text. The :- form supplies a fallback for an unset or empty value:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
file='report'
printf '%sn' "${file}.txt"
printf 'Output: %sn' "${OUTPUT_DIR:-.}"

Use the form that matches your intent: ${value:-fallback} uses the fallback if the value is unset or empty; ${value-fallback} uses it only if the value is unset.

Command substitution: capture command output

Use $(...) to run a command and substitute its standard output. This is the modern, readable form to prefer over backticks:

today=$(date +%F)
printf 'Date: %sn' "$today"

Quote the substitution when the captured output should remain one argument. Command substitution removes trailing newline characters from the output, so do not use it when preserving trailing newlines is important.

Use conditionals, loops, and case statements

Bash control flow combines shell syntax with commands that report success or failure. Bash’s [[ ... ]] and arithmetic (( ... )) constructs are Bash syntax; use them only when Bash is the intended interpreter.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Conditionals

An if condition runs a command or test. The branch is selected according to its exit status:

if [[ -f "$config" ]]; then
  printf 'Using %sn' "$config"
else
  printf 'No configuration file foundn' >&2
fi

Here, [[ -f "$config" ]] succeeds when the path names a regular file. The redirection >&2 sends the diagnostic to standard error.

Loops

A for loop iterates over a list of words. Quoted array expansion preserves each array element as one item:

files=(./reports/*.csv)
for file in "${files[@]}"; do
  printf 'Found: %sn' "$file"
done

A while loop is useful when a command should repeat while a condition succeeds:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
count=0
while (( count < 3 )); do
  printf 'Attempt %dn' "$count"
  ((count += 1))
done

Arithmetic commands have their own exit-status behavior: (( expression )) returns success when the expression evaluates to a nonzero value. In the loop above, the increment’s status is not being used as a condition; the next condition controls continuation.

Case statements

Use case when one value should select among several patterns:

case "${1:-}" in
  start|restart)
    printf 'Starting servicen'
    ;;
  stop)
    printf 'Stopping servicen'
    ;;
  *)
    printf 'Usage: %s {start|restart|stop}n' "$0" >&2
    exit 2
    ;;
esac

The pattern alternatives are separated by |, and each branch ends with ;;. The *) branch handles anything not matched above.

Organize scripts with functions, parameters, and arrays

Positional parameters

When Bash runs a script, $0 is its name and $1, $2, and so on are its positional parameters. Use "$@" to pass all of them onward while preserving each argument boundary:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
printf 'Script: %sn' "$0"
printf 'Arguments:n'
printf '  %sn' "$@"

By contrast, "$*" combines the positional parameters into one string. Choose based on whether the receiving command needs separate arguments.

Functions

Functions give a name to a reusable block of commands. Their arguments are available through the same positional parameter forms:

say() {
  local message=${1:-}
  printf '%sn' "$message"
}

say 'Backup complete'

local makes a variable local to the function, which helps prevent accidental changes to variables elsewhere in the script. It is a Bash feature, not a portable POSIX shell feature.

Arrays preserve argument boundaries

When building a command from a variable number of arguments, use a Bash array. Do not store quote marks in a scalar string and expect Bash to reinterpret them later as command-line quoting. ShellCheck’s array guidance illustrates the array approach; the relevant ShellCheck page is https://www.shellcheck.net/wiki/SC array.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
args=(--color=auto)
if [[ -n ${OUTPUT_FILE:-} ]]; then
  args+=(--output "$OUTPUT_FILE")
fi

some-command "${args[@]}" input.txt

Each array element stays a distinct argument in "${args[@]}", including a value such as monthly report.txt. Set an array element with args+=(value) to append it.

Handle input, output, and pipelines

Standard input (file descriptor 0), standard output (1), and standard error (2) are separate streams. Redirections control where those streams go; pipelines connect the standard output of one command to the standard input of the next.

Redirection

printf 'Run startedn' > run.log
printf 'Additional detailn' >> run.log
printf 'Warning: check the inputn' >&2

> creates or replaces a file, while >> appends. Redirecting to file descriptor 2 sends output to standard error.

Pipelines

grep -F 'ERROR' app.log | sort | uniq -c

This pipeline passes matching lines through sort, then counts repeated lines with uniq -c. By default, a pipeline’s status is the status of its last command. Bash’s set -o pipefail changes that behavior so a pipeline reports failure if a command in it fails; understand this option before relying on pipeline status in error handling.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Here-documents

A here-document feeds a block of text to a command’s standard input:

cat <<'EOF'
Configuration notes:
- Preserve this text literally.
EOF

Quoting the delimiter ('EOF') prevents parameter, command, and arithmetic expansion in the here-document body. An unquoted delimiter allows expansions, which can be useful for generating text with variable values.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Check failures deliberately; do not treat options as magic

A script should decide which failures it can handle and what it should do when they occur. An explicit conditional makes the expected failure path visible:

if cp -- "$source" "$destination"; then
  printf 'Copied %sn' "$source"
else
  status=$?
  printf 'Could not copy %s (status %s)n' "$source" "$status" >&2
  exit "$status"
fi

The else branch captures the failed command’s status before running another command. This lets the script report a useful error and preserve the original failure code.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Bash options such as set -e, set -u, and set -o pipefail affect how a script behaves. They are not a substitute for understanding each command’s status and the contexts in which Bash treats failures specially. Google’s Shell Style Guide advises choosing options so that invoking a script as bash script_name does not break its functionality. Prefer explicit checks for critical operations, and test the failure paths your script is expected to encounter.

Choose Bash or a portable shell on purpose

Before writing a script, decide whether it is specifically for Bash or must run under a POSIX shell. That choice determines the shebang, syntax you can use, the version you need, and the shell rules a linter should apply. ShellCheck notes that its guidance depends on the target shell; its documentation covers shell-specific syntax and the importance of specifying the shell.

Decision Bash-targeted script POSIX-shell-targeted script
Interpreter Use a Bash shebang, such as #!/usr/bin/env bash, when that lookup is appropriate for the target environment. Use a POSIX shell interpreter available on the target system, commonly #!/bin/sh.
Syntax Bash features such as arrays, [[ ... ]], and local are available, subject to the minimum Bash version. Avoid Bash-only syntax; use constructs specified for the POSIX shell.
Lint target Tell ShellCheck the script targets Bash, for example with a Bash shebang or an explicit shell selection. Tell ShellCheck the intended POSIX-style shell rather than letting it infer Bash.
Best fit Use when you control the environment and want Bash features or Bash is the project’s chosen executable shell. Use when the script must run in environments that provide a POSIX shell but may not provide Bash.

Google’s Shell Style Guide describes Bash as its organization’s choice for executables while acknowledging environments that require another shell. That is organizational guidance, not a universal portability guarantee. Verify the interpreter and minimum version actually present wherever your script will run.

Lint and consult the manual as you learn

ShellCheck is a static analysis tool for shell scripts. Its guidance spans beginner syntax issues, intermediate semantic problems, and advanced pitfalls. Run it with the intended shell in mind, then understand a warning before changing code: the goal is correct behavior for your target environment, not merely a clean report.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For detailed syntax and behavior, consult the GNU Bash Reference Manual. The GNU Bash search result identifies Edition 5.3 for Bash 5.3, last updated 18 May 2025. GNU also provides the manual in online and downloadable formats; the Free Software Foundation says printed copies of some GNU manuals are available for purchase, but that does not establish a current print edition or listing for Bash.

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.