Command_substitution
Chapter 29: Command Substitution in Bash
Section titled “Chapter 29: Command Substitution in Bash”Overview
Section titled “Overview”Command substitution allows you to capture the output of a command and use it as data within another command, variable, or expression. This is fundamental to creating dynamic bash scripts.
Basic Command Substitution
Section titled “Basic Command Substitution”Syntax
Section titled “Syntax”# Modern syntax (recommended)$(command)
# Legacy syntax (backticks)`command`Simple Examples
Section titled “Simple Examples”# Get current datecurrent_date=$(date)echo "$current_date"
# Get current usercurrent_user=$(whoami)echo "Logged in as: $current_user"
# Get current directorypwd=$(pwd)echo "In directory: $pwd"
# Same as backtickscurrent_date=`date`echo "$current_date"Why Use Command Substitution?
Section titled “Why Use Command Substitution?”┌────────────────────────────────────────────────────────────────┐│ Command Substitution Use Cases │├────────────────────────────────────────────────────────────────┤│ ││ Variable Assignment: ││ ─────────────────── ││ result=$(command) → Store command output ││ ││ In Strings: ││ ──────────── ││ echo "Date: $(date)" → Embed output in string ││ ││ In Conditions: ││ ──────────────── ││ if $(cmd); then... → Check command exit status ││ ││ Arithmetic: ││ ─────────── ││ count=$(($(wc -l file))) → Nested command substitution ││ ││ As Arguments: ││ ───────────── ││ rm -rf $(find /tmp -type f) → Use output as arguments ││ │└────────────────────────────────────────────────────────────────┘Variable Assignment
Section titled “Variable Assignment”Simple Assignment
Section titled “Simple Assignment”# String outputhostname=$(hostname)echo "$hostname"
# Single valueline_count=$(wc -l < file.txt)echo "Lines: $line_count"
# Multiple values (read into array)mapfile -t files < <(ls *.txt)echo "${files[0]}"Complex Output
Section titled “Complex Output”# Parse command output# Get third field from ps outputpid_info=$(ps -ef | grep nginx | awk 'NR==1 {print $2}')
# Get system uptimeuptime_info=$(uptime)echo "$uptime_info"Nested Command Substitution
Section titled “Nested Command Substitution”Multiple Levels
Section titled “Multiple Levels”# Nested commandsouter=$(echo $(echo inner))echo "$outer"
# Practical exampletotal_lines=$(($(wc -l *.txt | tail -1 | awk '{print $1}')))echo "Total lines: $total_lines"
# With datetimestamp=$(date +"%Y-%m-%d %H:%M:%S")echo "Current timestamp: $timestamp"Avoiding Nested Pitfalls
Section titled “Avoiding Nested Pitfalls”# Common mistake# Using backticks inside backticks (hard to read)result=`echo \`date\``
# Better: use modern syntaxresult=$(echo $(date))
# Or use variablestemp=$(date)result=$(echo $temp)Command Substitution in Strings
Section titled “Command Substitution in Strings”Simple Usage
Section titled “Simple Usage”# In echoecho "Today is $(date +%A)"echo "Hostname: $(hostname)"
# In double quotes (expansion happens)message="Running as $(whoami)"echo "$message"
# Multiple substitutionsinfo="User: $(whoami) on $(hostname) at $(date +%H:%M)"Here Documents
Section titled “Here Documents”# Using command substitution in here doccat << EOFReport generated: $(date)Current user: $(whoami)Hostname: $(hostname)Uptime: $(uptime)EOFPerformance Considerations
Section titled “Performance Considerations”Efficiency Tips
Section titled “Efficiency Tips”# Bad: Multiple command substitutions in loopfor i in {1..1000}; do val=$(date +%N) # Creates subshell each timedone
# Better: Use external reference when possiblefor i in {1..1000}; do val=$i # No subshelldone
# If command substitution needed# Use faster commandsecho $(date +%s) # Fast# vsecho $(date) # Slower (more output)Subshell Considerations
Section titled “Subshell Considerations”# Command substitution runs in subshell# Changes don't affect parentvalue=originalvalue=$(value=modified; echo "$value")echo "$value" # Still "original"
# Use process substitution for some caseswhile read line; do echo "$line"done < <(command)Real-World DevOps Examples
Section titled “Real-World DevOps Examples”System Information
Section titled “System Information”#!/usr/bin/env bash
# Get system informationhostname=$(hostname)kernel=$(uname -r)uptime_info=$(uptime -p)cpu_count=$(nproc)mem_total=$(free -h | awk '/^Mem:/ {print $2}')
echo "=== System Information ==="echo "Hostname: $hostname"echo "Kernel: $kernel"echo "Uptime: $uptime_info"echo "CPU Cores: $cpu_count"echo "Memory: $mem_total"Dynamic Filenames
Section titled “Dynamic Filenames”#!/usr/bin/env bash
# Create timestamped backuptimestamp=$(date +%Y%m%d_%H%M%S)backup_file="backup_${timestamp}.tar.gz"
tar -czf "$backup_file" /home/
# Rotating log fileslog_file="app_$(date +%Y%m%d).log"
# Unique filenamesunique_file="data_$(date +%s)_$$"Parsing Command Output
Section titled “Parsing Command Output”#!/usr/bin/env bash
# Get IP addressip_address=$(hostname -I | awk '{print $1}')echo "IP: $ip_address"
# Get disk usage percentagedisk_usage=$(df -h / | awk 'NR==2 {print $5}')echo "Disk: $disk_usage"
# Get memory usagemem_used=$(free | awk '/^Mem:/ {printf "%.1f", $3/1024/1024}')mem_total=$(free | awk '/^Mem:/ {printf "%.1f", $2/1024/1024}')echo "Memory: ${mem_used}GB / ${mem_total}GB"Docker Commands
Section titled “Docker Commands”#!/usr/bin/env bash
# Get container IDcontainer_id=$(docker ps --filter "name=myapp" --format "{{.ID}}")
# Get container IPcontainer_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $container_id)
# Get image tagimage_tag=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -1)Kubernetes Commands
Section titled “Kubernetes Commands”#!/usr/bin/env bash
# Get pod statuspod_status=$(kubectl get pod myapp -o jsonpath='{.status.phase}')
# Get node IPnode_ip=$(kubectl get node worker-1 -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}')
# Get container imageimage=$(kubectl get pod myapp -o jsonpath='{.spec.containers[0].image}')
# Count podspod_count=$(kubectl get pods -n default --no-headers | wc -l)AWS CLI Integration
Section titled “AWS CLI Integration”#!/usr/bin/env bash
# Get instance IDinstance_id=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=web-server" --query 'Reservations[0].Instances[0].InstanceId' --output text)
# Get S3 bucket regionregion=$(aws s3api get-bucket-location --bucket my-bucket --output text)
# Get AMI IDami_id=$(aws ec2 describe-images --filters "Name=name,Values=ubuntu-20.04*" --query 'Images[0].ImageId' --output text)Error Handling
Section titled “Error Handling”Capturing Exit Status
Section titled “Capturing Exit Status”# Command substitution doesn't affect $?output=$(command_that_might_fail)status=$?
if [[ $status -ne 0 ]]; then echo "Command failed with status $status"fi
# Better approachif output=$(command_that_might_fail 2>&1); then echo "Success: $output"else echo "Failed: $output"fiSuppressing Output
Section titled “Suppressing Output”# Suppress command output (still runs)result=$(command >/dev/null 2>&1)
# Check if command existsif command -v docker >/dev/null 2>&1; then echo "Docker is installed"fiCommon Pitfalls
Section titled “Common Pitfalls”Word Splitting
Section titled “Word Splitting”# Problem: output is split on whitespacefiles=$(ls -1)echo $files # Split into separate words!
# Solution: quote the variableecho "$files" # Preserves newlines
# Or use arraysmapfile -t files < <(ls -1)echo "${files[0]}"Trailing Newlines
Section titled “Trailing Newlines”# Command substitution removes trailing newlinesoutput=$(echo -e "line1\nline2\n")echo "$output" # No trailing newline
# If you need to preserveoutput=$(echo -e "line1\nline2\n"; printf "x")output=${output%x} # Remove trailing xBest Practices
Section titled “Best Practices”Use Modern Syntax
Section titled “Use Modern Syntax”# ✓ Recommendedresult=$(command)output=$(command with args)
# ✗ Avoid (hard to read/nest)result=`command`Quote Variables
Section titled “Quote Variables”# Always quote command substitutionecho "Result: $(command)"
# Unless you want word splittingfiles=$(ls -1)for f in $files; do # Intentionally unquoted echo "$f"doneCheck for Errors
Section titled “Check for Errors”# Verify command succeededresult=$(command) || { echo "Command failed" exit 1}
# Capture stderr tooresult=$(command 2>&1)Advanced Techniques
Section titled “Advanced Techniques”Process Substitution Alternative
Section titled “Process Substitution Alternative”# Instead of command substitution# Use process substitution when reading
# Command substitution - output stored in variablelines=$(cat file.txt)
# Process substitution - streamingwhile IFS= read -r line; do echo "$line"done < <(cat file.txt)Arrays from Command Output
Section titled “Arrays from Command Output”# Using mapfile/readarraymapfile -t lines < <(ls -1)
# Using read with process substitutionwhile IFS= read -r line; do lines+=("$line")done < <(find . -type f)Summary
Section titled “Summary”In this chapter, you learned:
- ✅ Basic command substitution syntax ($( ) and backticks)
- ✅ Variable assignment with command output
- ✅ Nested command substitution
- ✅ Using in strings and here documents
- ✅ Performance considerations
- ✅ Real-world DevOps examples
- ✅ Error handling
- ✅ Common pitfalls and best practices
- ✅ Advanced techniques
Next Steps
Section titled “Next Steps”Continue to the next chapter to learn about Exit Status and Error Handling.
Previous Chapter: Parameter Expansion Next Chapter: Exit Status and Error Handling