Bash Cheatsheet - Shell Scripting Command Reference
A script that runs is not the same as a script that is correct. Most bash failures in production come from three details: unquoted values that get word-split, an empty variable expanding into a malformed command, and a failing pipeline stage silently ignored because pipefail is off. The sheet covers six baseline groups — variables, conditionals, loops, functions, string operations and file tests — and closes with parameter expansion and debugging, the latter being what actually moves a script from running to correct. The variables group earns its place: ${var:-default} for a fallback, ${#var} for length and ${var//a/b} for global replacement handle cases that otherwise become chains of if statements. Within conditionals, [[ ]] beats [ ] because it skips word splitting and adds =~ matching plus && and || chaining. The functions group covers return conventions, the local keyword, and losing the quotes around "$@" — a common genuine bug. The debugging group covers set -x, set -u, set -euo pipefail and bash -n, and that last combo is worth making the first line of any script that is not throwaway.
Typical Use Case
Write scripts, process text and logs, batch rename and find files, compose pipelines on servers, and troubleshoot script permission, unexported variables, and unexpanded globs.
Variables 8
NAME="value"echo $NAMEecho ${NAME}readonly NAME="value"unset NAME${VAR:-default}${VAR:=default}${#VAR}Conditionals 7
if [ condition ]; then ... fiif [ -f file ]; then ... fiif [ $a -eq $b ]; then ... fiif [ "$str" = "value" ]; then ... fiif [[ $str =~ regex ]]; then ... fi[ -d dir ] && echo "exists"case $var in pattern1) ... ;; pattern2) ... ;; esacLoops 6
for i in 1 2 3; do echo $i; donefor ((i=0; i<10; i++)); do ... donewhile [ condition ]; do ... doneuntil [ condition ]; do ... donebreak / continueselect var in list; do ... doneFunctions 7
function name() { ... }name() { ... }name arg1 arg2$1 $2 $@$#return 0local var="value"String Operations 7
${VAR:offset:length}${VAR#pattern}${VAR##pattern}${VAR%pattern}${VAR%%pattern}${VAR/pattern/replacement}${VAR//pattern/replacement}File Tests 8
[ -f file ][ -d dir ][ -e path ][ -r file ][ -w file ][ -x file ][ -s file ][ file1 -nt file2 ]Parameter Expansion & Debugging (REQ-02 add-on) 12
${var:-default}${var:=default}${var:?message}${var#pattern}${var%pattern}${var/pattern/replace}${var^^}${#var}set -xbash -n script.shshopt -s nullglobgetopts "a:b" optParameter matrix
| Parameter | Effect | Example |
|---|---|---|
-x | Print each command as it runs, handy for debugging scripts | bash -x script.sh |
-e | Exit the script immediately when a command fails | set -e |
-u | Error out when an undefined variable is used | set -u |
-o pipefail | Treat the pipeline as failed if any stage fails | set -eo pipefail |
2> | Redirect standard error | cmd 2>/dev/null |
| | Pipe: the output of the previous command becomes the input of the next | cat log | grep error | wc -l |
$() | Command substitution: capture the output | now=$(date +%F) |
&& / || | Run the second command only if the first succeeds / fails | make && make install |
-name / -type | find by name / by type | find . -name "*.log" -mtime +7 |
-i | Ignore case (in grep and the like) | grep -ri error log/ |
Common pitfalls
SymptomThe script reports Permission denied and cannot run.
CauseThe file lacks the executable bit, or the shebang on the first line points to a nonexistent interpreter.
Fixchmod +x script.sh; make sure the first line is #!/usr/bin/env bash and the bash path exists.
SymptomThe variable is empty in a subshell or script.
CauseIt was not exported, or its scope is limited to the current shell.
FixUse export VAR=val to make it visible to child processes; declare variables consistently as local or global inside scripts.
SymptomThe for f in * glob does not expand and is treated as a literal.
CauseWhen the directory is empty and nullglob is off, * is passed to the command as-is.
FixEnable shopt -s nullglob; or handle the empty-directory case with find ... -print0 | xargs -0.
SymptomA redirection or pipe emptied the file.
CauseA command like cat file > file truncates the target before reading it.
FixRedirect to a different or temporary file and then overwrite; be careful about the direction of > and >>.
SymptomSpaces or special characters split a filename into multiple arguments.
CauseThe variable was not quoted, so IFS splits on spaces and tabs.
FixAlways wrap variables in "$var"; process filenames with while IFS= read -r line and find -print0.
Symptomrm deleted files it should not have.
CauseThe glob matched too broadly, or the path variable was empty.
Fixecho the matches before rm; add -i for confirmation on dangerous operations; avoid rm -rf "$dir"/* when the variable is empty.
Troubleshooting
1Debug each step of the script
bash -x script.shPrint the commands actually executed and the variable expansions to locate logic errors.
2Confirm the script has no syntax errors
bash -n script.shChecks syntax only without executing; good for validation before release.
3Locate large files modified recently
find /var -type f -size +100M -mtime -1Quickly surface suspicious files when investigating a disk spike.
4Count occurrences of errors in a log
grep -ci error /var/log/app.logQuickly gauge the scale of errors, and use tail to see the latest samples.
Command Examples
Default a possibly-unset variable
NAME=${NAME:-lzh}
echo "$NAME"${var:-default} takes default when the variable is unset or empty; ${var-default} takes it only when completely unset.
Output
lzh # outputs the default when NAME is unset; outputs the original value when set
Test whether a file exists
if [[ -f /etc/nginx.conf ]]; then
echo exists
else
echo missing
fiUse [[ ]] instead of [ ]; it supports && and || and does not split words incorrectly; always quote variables to avoid syntax errors on empty values.
Output
exists
Resolve the directory of the running script
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
echo "$DIR"Use BASH_SOURCE[0] rather than $0, so that even when the script is sourced you get its real directory.
Output
/home/deploy/scripts
Common Pitfalls
- Prefer [[ ]] and (( )) for tests and arithmetic; [ ] trips on empty variables or strings containing wildcards.
- Quoting variables is a hard rule: for i in $LIST or rm $f without quotes split words or delete the wrong files when spaces or globs are involved.
- set -e means fail fast: a failing line exits instead of continuing. Enable it in important scripts to avoid cascading failures.
- Variables in functions are global by default; forgetting local pollutes globals and setups with the same name are easy to hit.
- Both rm -rf and > redirection really delete or overwrite; echo the command as a preview or guard before running against production data.
Tips
- No spaces around = in variable assignment, or it will be interpreted as a command.
- Always quote variables ("$VAR") to avoid word splitting and special character issues.
- [ ] is syntactic sugar for the test command; [[ ]] is a Bash extension with more features like regex matching.
FAQ
What do set -euo pipefail do respectively at the top of a Bash script?
set -e exits on an error, but a failing command does not trigger the exit when it appears in a condition such as if/while/&&/|| or in the middle of a pipeline; -u makes referencing an undefined variable an error; -o pipefail makes a pipeline return the rightmost non-zero exit code among its commands rather than only the last one. Combined they surface errors early and are the standard for robust scripts.
Why does a variable expand inside double quotes but not single quotes in Bash?
Inside double quotes, $variables, $(commands), and backticks are expanded normally, whereas single quotes treat everything literally with no expansion, which suits paths, regexes, or text that must stay untouched. Always wrap a variable holding a path you want to keep intact in double quotes, or word splitting will break it apart.
What is the difference between $@ and $* in Bash?
Without quoting both are equivalent and split the arguments on whitespace. The decisive difference appears when quoted: the @ form preserves each argument as a separate word, whereas the * form joins all arguments into one string. Prefer the quoted at-sign form when iterating over positional parameters.
How do I read a file line by line in Bash robustly?
Use while IFS= read -r line; do ...; done < file. IFS= prevents stripping leading and trailing whitespace, and -r keeps backslashes literal. Do not use for line in $(cat file), which splits the file into words on whitespace and loses line structure.
How do I check whether the previous command succeeded, and how do I make my script fail on purpose in Bash?
$? holds the exit status of the immediately preceding command, where 0 means success and non-zero means failure, so copy it into a variable before checking. Use exit 1 to exit with a non-zero status deliberately, while exit 0 signals success. Combined with set -e, the script automatically aborts on any uncaught failure.
Official References
Each command links to its official documentation below, so you can verify the latest usage and read deeper.
Maintained by LaoHand
Publicly updated on Sep 10, 2026, continuously proofread against official docs.
Contact Us
Wrong command or description? Send us corrections, business inquiries or product feedback by email.
Contact Us