What are the key differences between sh and Bash in shell programming?
When writing shell programs, we often use /bin/sh and /bin/bash. I typically use bash, but I’m unclear about the differences between these two shell interpreters.
What are the main differences between Bash and sh?
What should developers be aware of when programming in Bash versus sh?
The key differences between sh (Bourne shell) and bash (Bourne Again Shell) lie in their feature sets, compatibility, and intended use. sh is the original Unix shell with minimal functionality, while bash is a feature-rich superset that includes advanced programming capabilities, command-line editing, and job control. When programming, developers should be aware that sh scripts are more portable across Unix systems but lack modern conveniences, whereas bash offers powerful features like arrays, string manipulation, and advanced control structures that make complex scripting easier but may not work on systems without bash installed.
Contents
- What is sh?
- What is bash?
- Key Feature Differences
- Compatibility and Portability
- When to Use Each Shell
- Practical Programming Examples
- Best Practices for Shell Scripting
What is sh?
The sh shell, originally developed by Stephen Bourne at Bell Labs in 1977, represents the foundation of Unix shell programming. As the original Unix shell, sh provides a minimal feature set focused on basic command execution and simple scripting capabilities. When you execute scripts with /bin/sh, you’re typically running either the original Bourne shell or a compatible variant like the Almquist shell (ash) or Debian Almquist shell (dash).
The basic characteristics of sh include:
- Simple syntax: Basic control structures like
if,for,while, andcase - Limited variable handling: No arrays, basic string operations only
- Minimal built-in commands: Focus on external command execution
- POSIX compliance: Adheres to the Portable Operating System Interface standard
The Bourne shell’s design philosophy emphasizes simplicity and reliability, making it ideal for system initialization scripts and environments where minimal resource usage is critical.
What is bash?
Bash, which stands for “Bourne Again Shell,” was created by Brian Fox for the GNU Project in 1989 as a free software replacement for the Bourne shell. As an enhanced version of sh, bash maintains backward compatibility while adding numerous powerful features that modern shell scripting demands.
Key characteristics of bash include:
- Rich feature set: Arrays, associative arrays, advanced string manipulation
- Interactive enhancements: Command-line editing, history expansion, tab completion
- Programming extensions: Functions, arithmetic evaluation, signal handling
- Job control: Background processes, process management
- POSIX compliance: Maintains compatibility with
shwhile adding extensions
Bash has become the default shell on most Linux distributions and macOS systems, making it the de facto standard for shell scripting in modern Unix-like environments.
Key Feature Differences
Variable Handling
sh: Supports only scalar variables with basic assignment and expansion:
name="John Doe"
echo $name
bash: Supports both scalar variables and arrays:
name="John Doe"
ages=(25 30 35)
echo ${ages[1]} # Output: 30
String Manipulation
sh: Limited to basic parameter expansion:
filename="document.txt"
echo ${filename%.txt} # Remove .txt suffix
bash: Advanced string operations with ${parameter//pattern/replacement}:
text="hello world"
echo ${text//hello/hello} # Replace hello with hello
Arithmetic Operations
sh: Requires external command expr:
result=$(expr 5 + 3)
bash: Built-in arithmetic evaluation:
result=$((5 + 3))
Functions and Scope
sh: Basic functions with limited scoping:
func() {
echo "Hello"
}
bash: Enhanced functions with local variables:
func() {
local name="John"
echo "Hello, $name"
}
Control Structures
sh: Basic if statements and loops:
if [ -f "file.txt" ]; then
echo "File exists"
fi
for i in 1 2 3; do
echo $i
done
bash: Extended control structures with [[ ]] and advanced loops:
if [[ -f "file.txt" && -r "file.txt" ]]; then
echo "File exists and is readable"
fi
for ((i=1; i<=3; i++)); do
echo $i
done
Compatibility and Portability
Portability Considerations
When choosing between sh and bash, portability becomes a critical factor:
shscripts: Run on virtually any Unix-like system, including embedded systems and minimal installationsbashscripts: Require bash to be installed, which is common on most systems but not universal
Shebang Line Choices
The choice of shebang line determines which shell interprets your script:
#!/bin/sh # Most portable, uses system's default sh
#!/bin/bash # Explicitly uses bash
#!/usr/bin/env bash # More portable, finds bash in PATH
Compatibility Mode
Bash can operate in POSIX-compatibility mode to behave more like sh:
#!/bin/bash --posix
Compatibility Trade-offs Table:
| Feature | sh | bash | Portability Impact |
|---|---|---|---|
| Arrays | ❌ | ✅ | High - bash scripts won’t run on systems without bash |
| Advanced strings | ❌ | ✅ | Medium - basic scripts often work with sh |
| Arithmetic | Limited | Built-in | Medium - expr works everywhere |
| Process substitution | ❌ | ✅ | High - bash-specific feature |
| Associative arrays | ❌ | ✅ | High - bash 4.0+ only |
When to Use Each Shell
Choose sh When:
- You need maximum portability across Unix systems
- Writing system initialization scripts (e.g.,
/etc/init.d/) - Working with embedded systems or minimal Linux distributions
- Maintaining legacy codebases that must run on older systems
- Creating scripts for Docker containers where minimal image size matters
Choose bash When:
- You need advanced features like arrays or complex string manipulation
- Developing for modern Linux distributions or macOS
- Creating interactive scripts or command-line tools
- Working with system administration tasks on standard desktop/server systems
- Leveraging bash-specific features like process substitution or coprocesses
Hybrid Approach
Many developers use a hybrid strategy:
#!/bin/bash
# Bash-specific features here
declare -A config
config[host]="example.com"
# Fallback to POSIX-compatible features
if [ "$BASH_VERSION" ]; then
echo "Running bash with version $BASH_VERSION"
else
echo "Running compatible shell"
fi
Practical Programming Examples
File Processing Example
sh version (limited):
#!/bin/sh
# Process files in current directory
for file in *; do
if [ -f "$file" ]; then
echo "Processing: $file"
# Basic operations only
fi
done
bash version (enhanced):
#!/bin/bash
# Process files with advanced features
for file in *; do
if [[ -f "$file" && "$file" == *.txt ]]; then
echo "Processing text file: $file"
# Advanced string manipulation
filename="${file%.*}"
echo "Base name: $filename"
# Array operations
files_processed+=("$file")
fi
done
echo "Total files processed: ${#files_processed[@]}"
Configuration Management
sh version:
#!/bin/sh
# Simple key-value parsing
parse_config() {
while IFS='=' read -r key value; do
case "$key" in
\#*) continue ;;
*) eval "$key=\"$value\"" ;;
esac
done < "$1"
}
bash version:
#!/bin/bash
# Advanced configuration with arrays and validation
declare -A config
parse_config() {
local line_num=0
while IFS='=' read -r key value; do
((line_num++))
# Skip comments and empty lines
[[ "$key" =~ ^[[:space:]]*# ]] && continue
[[ -z "$key" ]] && continue
# Validate key format
if [[ ! "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
echo "Warning: Invalid key '$key' at line $line_num" >&2
continue
fi
# Store in associative array
config["$key"]="$value"
done < "$1"
}
Best Practices for Shell Scripting
Portability Testing
Always test your scripts with different shells:
#!/bin/bash
# Test script compatibility
test_with_shell() {
local shell="$1"
echo "Testing with $shell..."
# Create temporary script
cat > /tmp/test_script.sh << 'EOF'
#!/bin/sh
echo "Hello from $0"
EOF
chmod +x /tmp/test_script.sh
$shell /tmp/test_script.sh
rm /tmp/test_script.sh
}
test_with_shell "sh"
test_with_shell "bash"
Conditional Feature Detection
Use feature detection rather than shell guessing:
#!/bin/bash
# Check for bash features
if [ "${BASH_VERSION}" ]; then
echo "Bash detected, using advanced features"
# Test for specific features
if declare -A test_array 2>/dev/null; then
echo "Associative arrays available"
fi
else
echo "Running in compatibility mode"
# Use POSIX-compatible alternatives
fi
Error Handling
Implement robust error handling:
#!/bin/bash
set -euo pipefail # Exit on error, undefined vars, pipe failures
error_handler() {
local exit_code=$?
echo "Error on line $1: Exit code $exit_code" >&2
exit $exit_code
}
trap 'error_handler $LINENO' ERR
# Your script code here
Documentation and Comments
Document shell-specific features:
#!/bin/bash
# This script uses bash-specific features:
# - Associative arrays (declare -A)
# - Advanced string manipulation (${var//pattern/replacement})
# - Arithmetic evaluation ((expression))
# For POSIX compatibility, use:
# #!/bin/sh
# # Replace bash-specific features with alternatives
Conclusion
The differences between sh and bash represent a fundamental choice between portability and power in shell programming. sh provides broad compatibility across Unix systems but limits your scripting capabilities, while bash offers powerful features that can significantly enhance your productivity but may not work on all systems.
When developing shell scripts, consider these key takeaways:
- Portability vs. Features: Choose
shfor maximum compatibility,bashfor advanced capabilities - Shebang Matters: Use
#!/bin/shfor portability,#!/bin/bashfor bash-specific features - Test Compatibility: Always test scripts with different shells when portability is critical
- Use Conditional Logic: Implement feature detection rather than shell version checking
- Document Dependencies: Clearly indicate which shell-specific features your scripts use
For developers starting new projects, bash is generally recommended for most use cases due to its rich feature set and widespread availability. However, for system initialization scripts, Docker containers, or deployment automation where maximum portability is essential, sh remains the safer choice.
The shell programming landscape continues to evolve, with alternatives like zsh and fish gaining popularity, but understanding the fundamental differences between sh and bash remains essential for any Unix developer working with command-line tools and automation.