Complete bash-script toolkit with generation and validation capabilities
97
97%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Risky
Do not use without reviewing
This guide covers frequent mistakes made in bash and shell scripts, their consequences, and how to fix them.
# Wrong
file=/path/with spaces/file.txt
cat $file # Breaks into multiple argumentsWord splitting and glob expansion cause unexpected behavior.
# Right
file="/path/with spaces/file.txt"
cat "$file"Always quote variable expansions unless you explicitly need word splitting.
# Wrong
cd /some/directory
rm -rf * # DANGEROUS if cd fails!Commands execute even if previous commands fail, potentially catastrophic.
# 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 *# Wrong (== not POSIX, may fail in sh)
if [ "$var" == "value" ]; then
echo "match"
fi# POSIX sh - use single =
if [ "$var" = "value" ]; then
echo "match"
fi
# Or use bash [[ ]] (bash only)
if [[ "$var" == "value" ]]; then
echo "match"
fi# Wrong - unnecessary cat
cat file.txt | grep pattern
cat file.txt | awk '{print $1}'Wastes a process, less efficient.
# Right
grep pattern file.txt
awk '{print $1}' file.txt
# Or use redirection
< file.txt grep pattern# Wrong
while read line; do
echo "$line"
done < fileBackslashes are interpreted, leading character may be removed.
# Right
while IFS= read -r line; do
echo "$line"
done < file-r prevents backslash interpretationIFS= prevents leading/trailing whitespace trimming# Wrong
command1
command2
if [ $? -eq 0 ]; then # Tests command2, not command1!
echo "Success"
fi# Right - test immediately
command1
if [ $? -eq 0 ]; then
echo "command1 succeeded"
fi
# Better - test directly
if command1; then
echo "Success"
fi#!/bin/sh
# Wrong - arrays not in POSIX sh
array=(one two three)
echo "${array[0]}"# Use bash
#!/bin/bash
array=(one two three)
echo "${array[0]}"
# Or use POSIX alternatives
set -- one two three
echo "$1"# Wrong - function not defined yet
my_function
my_function() {
echo "Hello"
}# Right - define first
my_function() {
echo "Hello"
}
my_function# DANGEROUS
user_input="$1"
eval "$user_input" # Command injection risk!Security vulnerability - arbitrary code execution.
# 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}"# Wrong - typo goes unnoticed
nmae="John" # Typo
echo "Hello, $name" # Prints "Hello, " (empty)# Right - use set -u
set -u
nmae="John" # Typo
echo "Hello, $name" # Error: name: unbound variable# Wrong - numeric comparison on strings
if [ "$version" -gt "2.0" ]; then
echo "New version"
fi# 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# Wrong
for file in $(ls *.txt); do
echo "$file"
doneFiles with spaces break into multiple items.
# 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)# Deprecated
result=`command arg1 arg2`# Modern
result=$(command arg1 arg2)$(cmd1 $(cmd2))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 shRecommendation: Use = for portability, or stick to == in bash with [[ ]].
# Wrong
script.sh "$@" # Right
command $@ # Wrong if args have spaces# Right
command "$@" # Preserves argument boundaries# Wrong
files=$(ls *.txt)
for file in $files; do
process "$file"
done# Right
for file in *.txt; do
process "$file"
done
# Or with find
find . -name "*.txt" -exec process {} \;# Wrong
function check_file() {
if [ -f "$1" ]; then
echo "File exists"
return 1 # Success should be 0!
fi
return 0
}# Right - 0 is success, non-zero is failure
function check_file() {
if [ -f "$1" ]; then
echo "File exists"
return 0
fi
return 1
}# Deprecated and error-prone
[ "$a" = "x" -a "$b" = "y" ]
[ "$a" = "x" -o "$b" = "y" ]# Right - use && and ||
[ "$a" = "x" ] && [ "$b" = "y" ]
[ "$a" = "x" ] || [ "$b" = "y" ]
# Or use [[ ]] in bash
[[ "$a" = "x" && "$b" = "y" ]]
[[ "$a" = "x" || "$b" = "y" ]]# Wrong
bash script.sh # Works but not ideal# Right
chmod +x script.sh
./script.shAnd include proper shebang:
#!/usr/bin/env bashSome tools expect files to end with a newline.
Ensure your editor adds a final newline, or:
echo "" >> file# Potentially inefficient
if [ "$(grep pattern file)" ]; then
echo "Found"
fi# Better - grep -q exits on first match
if grep -q pattern file; then
echo "Found"
fi# Wrong - doesn't match hidden files
for file in *; do
process "$file"
done# 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# Fails if no .txt files
for file in *.txt; do
process "$file" # Processes literal "*.txt"
done# 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# Dangerous
rm -rf "/$1" # What if $1 is empty or manipulated?# 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"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" ] # ExecutableBefore running a script, verify:
generator
validator