14.3. Shell Options and Subshells#
14.3.1. Common Pitfalls#
1. Subshell variables don’t affect parent
# Bad: Expecting variables to persist
(count=10)
echo $count # Empty, not 10
# Good: Use global scope if needed
count=10
{
((count++))
}
echo $count # 11
2. Combining set options incorrectly
# Bad: set -e causes issues with conditionals
set -e
if some_command; then
echo "Success"
fi # Script exits if some_command fails
# Better: Don't use with conditionals
set -e
some_command # Script exits if fails
echo "Success"
# Or disable for specific commands
set +e
some_command # Doesn't exit even if fails
set -e
3. Glob expansion surprises
# Bad: Filenames with spaces break
shopt -s nullglob
for file in *.txt; do
echo "$file" # May break if spaces (use quotes!)
done
# Good: Always quote
for file in *.txt; do
echo "$file" # Works with spaces if quoted
done
4. Process substitution not supported in older Bash
# Not available in Bash < 3.0
diff <(ls dir1) <(ls dir2) # May not work
# Portable alternative
diff <(ls dir1 | sort) <(ls dir2 | sort)
# Or use temp files
ls dir1 > /tmp/dir1.txt
ls dir2 > /tmp/dir2.txt
diff /tmp/dir1.txt /tmp/dir2.txt
5. Pipefail with set -e interaction
# Confusing: -e waits for pipe to finish
set -e
cat file.txt | head -1 | tail -1 # Returns 0 if tail succeeds (not head)
# Better: Use set -o pipefail
set -o pipefail
cat file.txt | grep error # Fails if grep finds nothing
14.3.2. Subshells and Process Substitution#
Subshells create isolated execution contexts, useful for cleanup and parallelism.
14.3.2.1. Subshell Basics#
#!/bin/bash
# Subshell with ()
echo "Parent PID: $$"
(echo "Subshell PID: $$") # Different PID
# Variables don't leak from subshells
var="parent"
(var="child"; echo $var) # child
echo $var # parent
# Subshell with { }
{ var="child"; echo $var; } # Runs in same shell (no subshell)
# cd in subshell doesn't affect parent
pwd # /home/user
(cd /tmp; pwd) # /tmp
pwd # /home/user (unchanged)
# Combining operations in subshell
(
cd /var/log
ls -la
grep error messages.log
) > /tmp/log_analysis.txt
# Background subshell
(
sleep 10
echo "Task complete"
) &
# Wait for background job
wait $!
14.3.2.2. Process Substitution#
#!/bin/bash
# Process substitution: <(command) creates file descriptor
diff <(ls dir1) <(ls dir2) # Compare directory contents
# Avoid temporary files
cat > config.txt << 'EOF'
file1
file2
file3
EOF
# Compare config with current files
comm -23 <(cat config.txt | sort) <(ls | sort)
# Multiple inputs
paste <(seq 1 5) <(echo A; echo B; echo C; echo D; echo E)
# Pipeline with multiple inputs
sort <(cat file1) <(cat file2) | uniq -d # Common lines
# Redirect multiple commands' output
cat <(echo "### Header") <(cat data.txt) > output.txt
# Process substitution in loops
while IFS= read -r file; do
echo "$file"
done < <(find . -type f -name "*.txt")
14.3.2.3. Subshell Use Cases#
#!/bin/bash
# Cleanup with trap in subshell
process_with_cleanup() {
local temp_dir
temp_dir=$(mktemp -d)
(
trap "rm -rf $temp_dir" EXIT
# Work in temp_dir
cp input.txt "$temp_dir/"
process "$temp_dir/input.txt"
# Automatic cleanup on exit
)
}
# Parallel processing with subshells
declare -a pids=()
for file in *.log; do
(
echo "Processing $file..."
analyze_log "$file"
) &
pids+=($!)
done
# Wait for all
for pid in "${pids[@]}"; do
wait $pid || echo "Process $pid failed"
done
# Isolate environment changes
(
set -e
set -u
cd /var/log
# environment changes are isolated
exec > /tmp/output.log 2>&1
)
# Parent shell environment unchanged
14.3.3. Shell Options (shopt)#
The shopt command controls optional Bash features, affecting script behavior and debugging.
14.3.3.1. Common Shell Options#
#!/bin/bash
# Enable option
shopt -s nullglob # Non-matching globs expand to empty (not literal)
shopt -s extglob # Extended pattern matching
shopt -s dotglob # Include dotfiles in glob expansion
# Disable option
shopt -u nocaseglob # Case-sensitive globbing
shopt -u huponexit # Don't HUP when shell exits
# Check option status
shopt -p nullglob # Print if set or unset
# List all options
shopt # Show all with status
# Practical options for robust scripts
#!/bin/bash
set -euo pipefail # Exit on error, undefined vars, pipe failure
shopt -s nullglob # Don't error on missing glob patterns
shopt -s failglob # Error if glob doesn't match
shopt -s dotglob # Include hidden files in globbing
14.3.3.2. Essential Script Options with set#
#!/bin/bash
# set -e: Exit if any command fails
set -e
gcc program.c || echo "Compilation failed" # Script exits here
echo "This won't run"
# set -u: Error on undefined variables
set -u
echo "$UNDEFINED_VAR" # Error: parameter not set
# set -o pipefail: Return last non-zero exit from pipe
set -o pipefail
cat nonexistent.txt | grep pattern # Returns error, not 0
# set -x: Print commands before execution (debugging)
set -x
ls -la # Output shows: + ls -la
ls # Output shows: + ls
# Combine multiple options
set -euxo pipefail # All of the above
# Unset options
set +e # Disable exit-on-error
set +u # Disable undefined variable check
14.3.3.3. Scoping and SHELLOPTS#
#!/bin/bash
# Current shell options are in SHELLOPTS
echo $SHELLOPTS # Shows current options
# Options in subshell
(
set -x # Only affects this subshell
echo "This is printed with +x"
)
# Back in parent shell, -x is not set
# Propagate options to subshells
export SHELLOPTS
(
echo "Has parent's options"
)
# Get option status programmatically
check_option() {
local opt=$1
local status=$(shopt -s "$opt" 2>/dev/null && echo "on" || echo "off")
echo "Option $opt is $status"
}
check_option nullglob
check_option dotglob