Common Shell Scripting Mistakes
This guide covers frequent mistakes made in bash and shell scripts, their consequences, and how to fix them.
Overview
Common Shell Scripting Mistakes
This guide covers frequent mistakes made in bash and shell scripts, their consequences, and how to fix them.
1. Unquoted Variables
Problem
# Wrong
file=/path/with spaces/file.txt
cat $file # Breaks into multiple arguments
Consequence
Word splitting and glob expansion cause unexpected behavior.
Solution
# Right
file="/path/with spaces/file.txt"
cat "$file"
Rule
Always quote variable expansions unless you explicitly need word splitting.
2. Not Checking Command Success
Problem
# Wrong
cd /some/directory
rm -rf * # DANGEROUS if cd fails!
Consequence
Commands execute even if previous commands fail, potentially catastrophic.
Solution
# Right
cd /some/directory || exit 1
rm -rf *
# Or use set -e
set -e
cd /some/directory
rm -rf *
# Or check explicitly
if ! cd /some/directory; then
echo "Failed to change directory" >&2
exit 1
fi
rm -rf *
3. Using [ ] with Bash Features
Problem
# Wrong (== not POSIX, may fail in sh)
if [ "$var" == "value" ]; then
echo "match"
fi
Solution
# POSIX sh - use single =
if [ "$var" = "value" ]; then
echo "match"
fi
# Or use bash [[ ]] (bash only)
if [[ "$var" == "value" ]]; then
echo "match"
fi
4. Useless Use of cat (UUOC)
Problem
# Wrong - unnecessary cat
cat file.txt | grep pattern
cat file.txt | awk '{print $1}'
Consequence
Wastes a process, less efficient.
Solution
# Right
grep pattern file.txt
awk '{print $1}' file.txt
# Or use redirection
< file.txt grep pattern
5. Not Using -r with read
Problem
# Wrong
while read line; do
echo "$line"
done < file
Consequence
Backslashes are interpreted, leading character may be removed.
Solution
# Right
while IFS= read -r line; do
echo "$line"
done < file
-rprevents backslash interpretationIFS=prevents leading/trailing whitespace trimming
6. Testing $? After Multiple Commands
Problem
# Wrong
command1
command2
if [ $? -eq 0 ]; then # Tests command2, not command1!
echo "Success"
fi
Solution
# Right - test immediately
command1
if [ $? -eq 0 ]; then
echo "command1 succeeded"
fi
# Better - test directly
if command1; then
echo "Success"
fi
7. Arrays in POSIX sh Scripts
Problem
#!/bin/sh
# Wrong - arrays not in POSIX sh
array=(one two three)
echo "${array[0]}"
Solution
# Use bash
#!/bin/bash
array=(one two three)
echo "${array[0]}"
# Or use POSIX alternatives
set -- one two three
echo "$1"
8. Not Declaring Functions Before Use
Problem
# Wrong - function not defined yet
my_function
my_function() {
echo "Hello"
}
Solution
# Right - define first
my_function() {
echo "Hello"
}
my_function
9. Using eval Unsafely
Problem
# DANGEROUS
user_input="$1"
eval "$user_input" # Command injection risk!
Consequence
Security vulnerability - arbitrary code execution.
Solution
# Avoid eval when possible
# If necessary, sanitize input thoroughly
# Or use safer alternatives
# Example: dynamic variable names
var_name="my_var"
# Don't: eval "echo \\$$var_name"
# Do: Use indirect expansion (bash)
echo "${!var_name}"
10. Forgetting set -u
Problem
# Wrong - typo goes unnoticed
nmae="John" # Typo
echo "Hello, $name" # Prints "Hello, " (empty)
Solution
# Right - use set -u
set -u
nmae="John" # Typo
echo "Hello, $name" # Error: name: unbound variable
11. Incorrect String Comparison
Problem
# Wrong - numeric comparison on strings
if [ "$version" -gt "2.0" ]; then
echo "New version"
fi
Solution
# Right - string comparison
if [ "$version" = "2.0" ]; then
echo "Exact match"
fi
# Or use proper version comparison
if [[ "$version" > "2.0" ]]; then
echo "Greater"
fi
12. Not Handling Spaces in Filenames
Problem
# Wrong
for file in $(ls *.txt); do
echo "$file"
done
Consequence
Files with spaces break into multiple items.
Solution
# Right - use glob directly
for file in *.txt; do
echo "$file"
done
# Or use find with -print0
while IFS= read -r -d '' file; do
echo "$file"
done < <(find . -name "*.txt" -print0)
13. Backticks Instead of $()
Problem
# Deprecated
result=`command arg1 arg2`
Solution
# Modern
result=$(command arg1 arg2)
Why
- Better nesting:
$(cmd1 $(cmd2)) - Better readability
- Fewer escaping issues
14. Using = Instead of == in [[ ]]
Not really a mistake, but inconsistent:
# Both work in [[ ]]
[[ "$var" = "value" ]] # POSIX style (works)
[[ "$var" == "value" ]] # Bash style (also works)
# Only = works in [ ]
[ "$var" = "value" ] # Works
[ "$var" == "value" ] # May fail in POSIX sh
Recommendation: Use = for portability, or stick to == in bash with [[ ]].
15. Not Quoting $@
Problem
# Wrong
script.sh "$@" # Right
command $@ # Wrong if args have spaces
Solution
# Right
command "$@" # Preserves argument boundaries
16. Using ls to Process Files
Problem
# Wrong
files=$(ls *.txt)
for file in $files; do
process "$file"
done
Issues
- Breaks on spaces
- Breaks on newlines in filenames
- Breaks on glob characters
Solution
# Right
for file in *.txt; do
process "$file"
done
# Or with find
find . -name "*.txt" -exec process {} \\;
17. Incorrect Exit Codes
Problem
# Wrong
function check_file() {
if [ -f "$1" ]; then
echo "File exists"
return 1 # Success should be 0!
fi
return 0
}
Solution
# Right - 0 is success, non-zero is failure
function check_file() {
if [ -f "$1" ]; then
echo "File exists"
return 0
fi
return 1
}
18. Using -a and -o in [ ]
Problem
# Deprecated and error-prone
[ "$a" = "x" -a "$b" = "y" ]
[ "$a" = "x" -o "$b" = "y" ]
Solution
# Right - use && and ||
[ "$a" = "x" ] && [ "$b" = "y" ]
[ "$a" = "x" ] || [ "$b" = "y" ]
# Or use [[ ]] in bash
[[ "$a" = "x" && "$b" = "y" ]]
[[ "$a" = "x" || "$b" = "y" ]]
19. Not Making Scripts Executable
Problem
# Wrong
bash script.sh # Works but not ideal
Solution
# Right
chmod +x script.sh
./script.sh
And include proper shebang:
#!/usr/bin/env bash
20. Forgetting Final Newline
Problem
Some tools expect files to end with a newline.
Solution
Ensure your editor adds a final newline, or:
echo "" >> file
21. Using grep -q Without Knowing Implications
Problem
# Potentially inefficient
if [ "$(grep pattern file)" ]; then
echo "Found"
fi
Solution
# Better - grep -q exits on first match
if grep -q pattern file; then
echo "Found"
fi
22. Incorrect glob Pattern
Problem
# Wrong - doesn't match hidden files
for file in *; do
process "$file"
done
Solution
# Include hidden files (bash)
shopt -s dotglob
for file in *; do
process "$file"
done
shopt -u dotglob
# Or explicitly
for file in * .[!.]* ..?*; do
[ -e "$file" ] && process "$file"
done
23. Not Handling Empty Globs
Problem
# Fails if no .txt files
for file in *.txt; do
process "$file" # Processes literal "*.txt"
done
Solution
# Bash - fail gracefully
shopt -s nullglob
for file in *.txt; do
process "$file"
done
shopt -u nullglob
# POSIX - check existence
for file in *.txt; do
[ -e "$file" ] || continue
process "$file"
done
24. Not Sanitizing Input
Problem
# Dangerous
rm -rf "/$1" # What if $1 is empty or manipulated?
Solution
# Safer
if [ -z "$1" ]; then
echo "Error: No argument provided" >&2
exit 1
fi
# Validate
case "$1" in
/*)
echo "Error: Absolute paths not allowed" >&2
exit 1
;;
esac
rm -rf "$1"
25. Using -e for File Existence
Not a mistake, but be specific:
[ -e "$file" ] # Exists (any type)
[ -f "$file" ] # Regular file
[ -d "$file" ] # Directory
[ -L "$file" ] # Symbolic link
[ -r "$file" ] # Readable
[ -w "$file" ] # Writable
[ -x "$file" ] # Executable
Quick Checklist
Before running a script, verify:
- Proper shebang (#!/bin/bash or #!/bin/sh)
- set -euo pipefail (strict mode)
- All variables quoted
- Error handling for critical commands
- Using $() not backticks
- Not using ls for file processing
- Functions defined before use
- Proper exit codes (0 = success)
- Input validation
- ShellCheck passes
Resources
- ShellCheck - Catches most of these
- Bash Pitfalls
- POSIX Shell