Complete bash-script toolkit with generation and validation capabilities
71
89%
Does it follow best practices?
Impact
—
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: