Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
A Linux shell script is a plain-text file containing commands that a shell executes in sequence. This guide uses Bash for its examples: create a file with a Bash shebang, add commands, check its syntax, run it with bash or directly after granting execute permission, then extend it with variables, arguments, conditions, loops, and error handling.
What a shell script is
A shell is a command interpreter such as Bash, Dash, Zsh, or KornShell. A shell command is one instruction typed at a prompt. A shell script is a text file containing commands that run non-interactively. A Bash script is a shell script that depends on Bash features.
Bash can read commands from a file and execute them in a non-interactive shell. The GNU Bash manual describes this file-based execution model.
What you need
- A Linux terminal
- Bash (check with
bash --version) - A text editor such as
nano - Optional: ShellCheck for static analysis
Create your first Bash script
- Open a terminal editor:
nano hello.sh. - Enter this content:
#!/usr/bin/env bash printf 'Hello, Linux!n' - Save with
Ctrl+O, pressEnter, then exit withCtrl+X.
The #!/usr/bin/env bash line is the shebang. It tells the operating system which interpreter to use when the file is executed directly. The .sh suffix is only a naming convention; the shebang and file contents determine interpretation.
#1 Best Overall
You can create the same file without an editor:
cat > hello.sh <<'EOF'
#!/usr/bin/env bash
printf 'Hello, Linux!n'
EOF
Commands such as cat, chmod, and ./hello.sh are entered at the terminal. The lines between EOF markers become the script.
Run the script
Invoke Bash explicitly
bash hello.sh
This works even when the file is not executable because Bash reads it as input.
Execute it directly
chmod u+x hello.sh
./hello.sh
chmod adds the execute bit. Equivalent common modes are:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →chmod +x hello.sh # Add execute permission according to the umask
chmod 755 hello.sh # Owner reads/writes/executes; others read/execute
chmod 700 hello.sh # Only the owner reads/writes/executes
Avoid chmod 777 as a routine fix: it grants unnecessary access. Direct execution also requires a valid shebang, an available interpreter, and a filesystem that permits execution.
Use a path
Most shells do not search the current directory automatically. Therefore hello.sh may produce “command not found,” while ./hello.sh works. You can also use an absolute path such as /home/alex/scripts/hello.sh, or install a script in a directory listed in $PATH.
Bash or POSIX sh?
Use #!/usr/bin/env bash when you need Bash features. Use #!/bin/sh only when you intentionally write POSIX-compatible shell code. On some distributions, including Ubuntu, /bin/sh points to Dash rather than Bash; see Ubuntu’s Dash documentation.
Rank #2
| Choose | Features and trade-off |
|---|---|
#!/usr/bin/env bash |
[[ ... ]], arrays, (( ... )), local, Bash parameter expansions, and pipefail; requires Bash. |
#!/bin/sh |
POSIX syntax and utilities; generally more portable, but without Bash-only features. |
ShellCheck’s SC2039 explains why Bash syntax in an sh script can fail on another machine. A shebang also helps ShellCheck infer the intended shell (SC2148).
The Tool Desk
Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Organize a readable script
#!/usr/bin/env bash
# Describe the script's purpose
main() {
printf 'Running the script...n'
}
main "$@"
Use comments for intent, variables for values, functions for reusable operations, a clear main path, and meaningful exit statuses. A function’s local variables are Bash-specific.
Variables, quoting, and command substitution
#!/usr/bin/env bash
name="Ada"
today="$(date +%F)"
printf 'Hello, %s! Today is %s.n' "$name" "$today"
Assignments have no spaces around =. Read values with $name or ${name}. The $(...) form captures command output and is preferred over legacy backticks.
Quote expansions used as command arguments. Unquoted text can undergo word splitting and wildcard expansion, as documented in ShellCheck SC2086:
# Unsafe when the name contains spaces or wildcard characters
rm $file
# Safer
rm -- "$file"
Do not quote syntax where intentional splitting is required. For a list of arguments, arrays are safer:
options=(-j 5 -B)
make "${options[@]}" file
Pass arguments to a script
#!/usr/bin/env bash
printf 'Script name: %sn' "$0"
printf 'First argument: %sn' "$1"
printf 'Argument count: %sn' "$#"
for arg in "$@"; do
printf 'Argument: %sn' "$arg"
done
Here, $0 is the invocation name or path, $1 and later variables are positional arguments, $# is the count, and "$@" preserves each argument as a separate item. Run it with ./greet.sh "Ada Lovelace". $? contains the previous command’s exit status.
Conditions and loops
Test files with Bash conditions
if [[ -f "$1" ]]; then
printf '%s is a regular filen' "$1"
else
printf 'File not found: %sn' "$1" >&2
exit 1
fi
[[ ... ]] is Bash syntax. Common tests include -e (any directory entry), -f (regular file), -d (directory), -r (readable), -x (executable), and [[ "$a" == "$b" ]] (string comparison). POSIX code instead uses forms such as [ -f "$1" ].
Loop over files
for file in "$HOME"/*.log; do
[[ -e "$file" ]] || continue
printf 'Log: %sn' "$file"
done
Without the existence check, an unmatched glob can remain a literal pattern.
Use arithmetic in a while loop
count=1
while (( count <= 3 )); do
printf 'Count: %sn' "$count"
((count++))
done
(( ... )) is Bash-specific.
Functions and validation
backup_file() {
local source_file=$1
local destination=$2
cp -- "$source_file" "$destination"
}
backup_file "notes.txt" "notes.txt.bak"
Validate arguments before using them, and send usage or error messages to standard error:
Recommended Free Tools
#!/usr/bin/env bash
if (($# != 1)); then
printf 'Usage: %s FILEn' "$0" >&2
exit 1
fi
file=$1
if [[ ! -f "$file" ]]; then
printf 'Error: not a regular file: %sn' "$file" >&2
exit 1
fi
printf 'Processing %sn' "$file"
Exit statuses and error handling
Every command returns a status: zero conventionally means success; a nonzero value indicates failure. Check operations whose failure matters:
if cp -- "$source" "$destination"; then
printf 'Backup createdn'
else
printf 'Backup failedn' >&2
exit 1
fi
set -u treats unset variables as errors. Bash’s set -o pipefail makes a pipeline report failure when an earlier component fails. These options are shell-dependent; SC3040 documents portability concerns. set -e is context-dependent and does not mean every failure always terminates the script. Prefer explicit checks instead of treating set -euo pipefail as a universal safety switch.
Redirection and pipelines
command > output.txt # Replace standard output
command >> output.txt # Append output
command 2> errors.txt # Redirect standard error
command >all.log 2>&1 # Send both streams to one file
command | grep pattern # Pipe output
For POSIX sh, use command >log 2>&1; Bash-only command &> log is not portable (SC3020).
Rank #4
Check, debug, and test
- Check syntax without running:
bash -n script.sh. - Trace executed commands:
bash -x script.sh. - Run static analysis:
shellcheck script.shorshellcheck -s bash script.sh.
ShellCheck finds common mistakes and portability hazards; it cannot prove that your business logic is correct. Test missing arguments, missing or unreadable files, absent commands, empty directories, filenames beginning with -, spaces, tabs, newlines, wildcard characters, and execution from a different working directory:
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
./script.sh "file with spaces.txt"
./script.sh "*.txt"
./script.sh ""
A practical file-inspection script
#!/usr/bin/env bash
set -u
set -o pipefail
usage() {
printf 'Usage: %s FILEn' "$0" >&2
}
if (($# != 1)); then
usage
exit 1
fi
file=$1
if [[ ! -f "$file" ]]; then
printf 'Error: file does not exist or is not a regular file: %sn' "$file" >&2
exit 1
fi
printf 'File: %sn' "$file"
printf 'Size: %s bytesn' "$(wc -c < "$file")"
Save it as inspect.sh, check it, then run:
bash -n inspect.sh
chmod u+x inspect.sh
./inspect.sh "notes with spaces.txt"
Paths and working directories
./script.sh does not mean “run relative to the script’s own directory”; it means relative to the terminal’s current directory. Cron, services, SSH sessions, and CI jobs may start elsewhere and have a different $PATH.
When a Bash script must locate files beside itself, construct its directory deliberately:
script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
Use this Bash-specific technique only when needed, and use absolute or deliberately constructed paths for important files.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Common errors
“Permission denied”
Run chmod u+x script.sh. If bash script.sh works but ./script.sh does not, inspect the execute bit, shebang, and whether the filesystem is mounted with execution disabled.
“Command not found”
The program may be missing, outside $PATH, misspelled, or being referenced from the wrong working directory. Diagnose with:
Best Value
command -v program
printf '%sn' "$PATH"
pwd
“Bad interpreter: No such file or directory”
The shebang may name a nonexistent interpreter, or the file may contain Windows CRLF endings. Inspect with:
command -v bash
file script.sh
sed -n '1p' script.sh | cat -A
Where appropriate, remove carriage returns with sed -i 's/r$//' script.sh.
“Syntax error near unexpected token”
Typical causes are Bash syntax being run by sh, an unmatched quote or delimiter, or CRLF endings. Run bash -n script.sh and ensure the declared interpreter matches the syntax.
PC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteSecurity rules worth adopting
- Quote variable expansions and use
--before user-controlled filenames where supported. - Never use
evalwith untrusted input or build shell commands by concatenating input. - Inspect scripts copied from the internet before running them, especially with
sudo,rm, recursive operations, or ownership changes. - Use unpredictable temporary-file creation methods rather than predictable names.
- Do not expose secrets in command-line arguments, logs, or
bash -xtraces. - Validate destructive targets and consider confirmation or a dry-run mode.
When shell is not the best tool
Shell excels at orchestrating existing command-line programs. Choose Python, Go, or another language for complex data structures, substantial JSON or CSV processing, sophisticated recovery logic, cross-platform applications, large parsers, networking, unit-test-heavy code, or performance-sensitive workloads.
FAQ
Do shell scripts need a .sh extension?
No. The extension helps humans recognize the file; permissions, the shebang, and the chosen interpreter control execution.
How can I check which Bash version is installed?
Run bash --version. The GNU manual’s current edition is Bash 5.3, updated May 18, 2025, but distributions may ship different versions.
How do I make a script available as a command?
Place an executable copy in a user-owned directory included in $PATH, then invoke it by name. Avoid relying on the current directory.
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.

