Skip to content

Command_substitution

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.


Terminal window
# Modern syntax (recommended)
$(command)
# Legacy syntax (backticks)
`command`
Terminal window
# Get current date
current_date=$(date)
echo "$current_date"
# Get current user
current_user=$(whoami)
echo "Logged in as: $current_user"
# Get current directory
pwd=$(pwd)
echo "In directory: $pwd"
# Same as backticks
current_date=`date`
echo "$current_date"

┌────────────────────────────────────────────────────────────────┐
│ 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 │
│ │
└────────────────────────────────────────────────────────────────┘

Terminal window
# String output
hostname=$(hostname)
echo "$hostname"
# Single value
line_count=$(wc -l < file.txt)
echo "Lines: $line_count"
# Multiple values (read into array)
mapfile -t files < <(ls *.txt)
echo "${files[0]}"
Terminal window
# Parse command output
# Get third field from ps output
pid_info=$(ps -ef | grep nginx | awk 'NR==1 {print $2}')
# Get system uptime
uptime_info=$(uptime)
echo "$uptime_info"

Terminal window
# Nested commands
outer=$(echo $(echo inner))
echo "$outer"
# Practical example
total_lines=$(($(wc -l *.txt | tail -1 | awk '{print $1}')))
echo "Total lines: $total_lines"
# With date
timestamp=$(date +"%Y-%m-%d %H:%M:%S")
echo "Current timestamp: $timestamp"
Terminal window
# Common mistake
# Using backticks inside backticks (hard to read)
result=`echo \`date\``
# Better: use modern syntax
result=$(echo $(date))
# Or use variables
temp=$(date)
result=$(echo $temp)

Terminal window
# In echo
echo "Today is $(date +%A)"
echo "Hostname: $(hostname)"
# In double quotes (expansion happens)
message="Running as $(whoami)"
echo "$message"
# Multiple substitutions
info="User: $(whoami) on $(hostname) at $(date +%H:%M)"
Terminal window
# Using command substitution in here doc
cat << EOF
Report generated: $(date)
Current user: $(whoami)
Hostname: $(hostname)
Uptime: $(uptime)
EOF

Terminal window
# Bad: Multiple command substitutions in loop
for i in {1..1000}; do
val=$(date +%N) # Creates subshell each time
done
# Better: Use external reference when possible
for i in {1..1000}; do
val=$i # No subshell
done
# If command substitution needed
# Use faster commands
echo $(date +%s) # Fast
# vs
echo $(date) # Slower (more output)
Terminal window
# Command substitution runs in subshell
# Changes don't affect parent
value=original
value=$(value=modified; echo "$value")
echo "$value" # Still "original"
# Use process substitution for some cases
while read line; do
echo "$line"
done < <(command)

#!/usr/bin/env bash
# Get system information
hostname=$(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"
#!/usr/bin/env bash
# Create timestamped backup
timestamp=$(date +%Y%m%d_%H%M%S)
backup_file="backup_${timestamp}.tar.gz"
tar -czf "$backup_file" /home/
# Rotating log files
log_file="app_$(date +%Y%m%d).log"
# Unique filenames
unique_file="data_$(date +%s)_$$"
#!/usr/bin/env bash
# Get IP address
ip_address=$(hostname -I | awk '{print $1}')
echo "IP: $ip_address"
# Get disk usage percentage
disk_usage=$(df -h / | awk 'NR==2 {print $5}')
echo "Disk: $disk_usage"
# Get memory usage
mem_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"
#!/usr/bin/env bash
# Get container ID
container_id=$(docker ps --filter "name=myapp" --format "{{.ID}}")
# Get container IP
container_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $container_id)
# Get image tag
image_tag=$(docker images --format "{{.Repository}}:{{.Tag}}" | head -1)
#!/usr/bin/env bash
# Get pod status
pod_status=$(kubectl get pod myapp -o jsonpath='{.status.phase}')
# Get node IP
node_ip=$(kubectl get node worker-1 -o jsonpath='{.status.addresses[?(@.type=="InternalIP")].address}')
# Get container image
image=$(kubectl get pod myapp -o jsonpath='{.spec.containers[0].image}')
# Count pods
pod_count=$(kubectl get pods -n default --no-headers | wc -l)
#!/usr/bin/env bash
# Get instance ID
instance_id=$(aws ec2 describe-instances --filters "Name=tag:Name,Values=web-server" --query 'Reservations[0].Instances[0].InstanceId' --output text)
# Get S3 bucket region
region=$(aws s3api get-bucket-location --bucket my-bucket --output text)
# Get AMI ID
ami_id=$(aws ec2 describe-images --filters "Name=name,Values=ubuntu-20.04*" --query 'Images[0].ImageId' --output text)

Terminal window
# Command substitution doesn't affect $?
output=$(command_that_might_fail)
status=$?
if [[ $status -ne 0 ]]; then
echo "Command failed with status $status"
fi
# Better approach
if output=$(command_that_might_fail 2>&1); then
echo "Success: $output"
else
echo "Failed: $output"
fi
Terminal window
# Suppress command output (still runs)
result=$(command >/dev/null 2>&1)
# Check if command exists
if command -v docker >/dev/null 2>&1; then
echo "Docker is installed"
fi

Terminal window
# Problem: output is split on whitespace
files=$(ls -1)
echo $files # Split into separate words!
# Solution: quote the variable
echo "$files" # Preserves newlines
# Or use arrays
mapfile -t files < <(ls -1)
echo "${files[0]}"
Terminal window
# Command substitution removes trailing newlines
output=$(echo -e "line1\nline2\n")
echo "$output" # No trailing newline
# If you need to preserve
output=$(echo -e "line1\nline2\n"; printf "x")
output=${output%x} # Remove trailing x

Terminal window
# ✓ Recommended
result=$(command)
output=$(command with args)
# ✗ Avoid (hard to read/nest)
result=`command`
Terminal window
# Always quote command substitution
echo "Result: $(command)"
# Unless you want word splitting
files=$(ls -1)
for f in $files; do # Intentionally unquoted
echo "$f"
done
Terminal window
# Verify command succeeded
result=$(command) || {
echo "Command failed"
exit 1
}
# Capture stderr too
result=$(command 2>&1)

Terminal window
# Instead of command substitution
# Use process substitution when reading
# Command substitution - output stored in variable
lines=$(cat file.txt)
# Process substitution - streaming
while IFS= read -r line; do
echo "$line"
done < <(cat file.txt)
Terminal window
# Using mapfile/readarray
mapfile -t lines < <(ls -1)
# Using read with process substitution
while IFS= read -r line; do
lines+=("$line")
done < <(find . -type f)

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

Continue to the next chapter to learn about Exit Status and Error Handling.


Previous Chapter: Parameter Expansion Next Chapter: Exit Status and Error Handling