NeuroAgent

Debug Shell Scripts: Echo Commands with Variables

Learn how to echo shell commands with variable expansion in your scripts. Discover the set -x debug mode and alternative methods for comprehensive command logging and debugging.

How to echo shell commands as they are executed in a shell script

In a shell script, how do I echo all shell commands called and expand any variable names? For example, given the line ls $DIRNAME, I would like the script to run the command and display ls /full/path/to/some/dir. The purpose is to save a log of all shell commands called and their arguments. Is there a better way of generating such a log?

NeuroAgent

The most effective way to echo shell commands as they are executed with variable expansion is to use the set -x debug mode in your shell script. This built-in shell option displays each command and its expanded arguments before execution, making it perfect for logging and debugging purposes.

Contents

Using set -x for Command Debugging

The set -x command is the built-in shell option that enables debug mode, which displays each command and its expanded arguments as the script executes. This is the most straightforward method for echoing shell commands with their actual arguments.

bash
#!/bin/bash

# Enable debug mode to echo all commands
set -x

# Example commands
echo "Starting script"
ls -l /tmp
cp file1.txt file2.txt
rm file1.txt

When this script runs, you’ll see each command line with variables expanded before it’s executed:

+ echo 'Starting script'
Starting script
+ ls -l /tmp
total 8
drwxrwxrwt 1 root root 4096 Jan  1 12:00 .
drwxr-xr-x 1 root root 4096 Jan  1 12:00 ..
+ cp file1.txt file2.txt
+ rm file1.txt

To temporarily disable debug mode within your script, use set +x:

bash
#!/bin/bash

set -x  # Enable debug mode
echo "Debug mode is ON"
set +x  # Disable debug mode
echo "Debug mode is OFF"
set -x  # Re-enable debug mode
echo "Debug mode is back ON"

Variable Expansion in Debug Mode

One of the key benefits of set -x is that it automatically shows variables in their expanded form, which is exactly what you requested. Variables are replaced with their actual values before the command is displayed.

bash
#!/bin/bash

set -x

DIRNAME="/usr/local/bin"
FILENAME="config.txt"
DEST_DIR="/backup"

ls -l "$DIRNAME"
cp "$FILENAME" "$DEST_DIR"

The output will show expanded variables:

+ ls -l /usr/local/bin
total 2048
-rwxr-xr-x 1 root root 12345 Jan  1 12:00 bash
-rwxr-xr-x 1 root root 67890 Jan  1 12:00 python
+ cp config.txt /backup

For more detailed variable tracking, you can also add explicit echo statements:

bash
#!/bin/bash

set -x

# Define variables
SOURCE_DIR="/var/log"
BACKUP_DIR="/backups/logs"
DATE=$(date +%Y%m%d)

# Show variable values
echo "Variables: SOURCE_DIR=$SOURCE_DIR, BACKUP_DIR=$BACKUP_DIR, DATE=$DATE"

# Commands with variables expanded
tar -czf "$BACKUP_DIR/logs_$DATE.tar.gz" -C "$SOURCE_DIR" .

Logging Commands to Files

To save the debug output to a file instead of just displaying it in the terminal, you can use shell redirection:

bash
#!/bin/bash

# Enable debug mode and redirect output to a log file
set -x 2> execution.log

echo "Starting backup process"
rsync -avz /source/ /destination/
echo "Backup completed"

# Or redirect both stdout and stderr
set -x &> execution.log

For more sophisticated logging with timestamps:

bash
#!/bin/bash

# Custom logging function
log_command() {
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    echo "[$timestamp] $*" | tee -a script.log
}

# Log each command before execution
log_command "Starting script"
log_command "Processing files"
log_command "ls -l /tmp"
log_command "cp file1.txt file2.txt"

# Alternative using trap for automatic command logging
trap 'echo "[DEBUG] $BASH_COMMAND"' DEBUG

Alternative Approaches

While set -x is the most common method, there are several alternatives for logging shell commands:

Using bash -x for Script Execution

Run the entire script with debug mode enabled:

bash
bash -x myscript.sh

Or run it and save to a file:

bash
bash -x myscript.sh > debug.log 2>&1

Using the PS4 Variable for Custom Debug Output

The PS4 variable defines the prefix displayed for debug traces. You can customize it to include timestamps or other information:

bash
#!/bin/bash

PS4='+${BASH_SOURCE}:${LINENO}: '
set -x

echo "Starting process"
ls /tmp

Using strace for System Call Tracing

For more detailed execution logging, you can use strace (though this shows system calls rather than shell commands):

bash
strace -f -o strace.log bash myscript.sh

Using script Command for Session Recording

bash
script -a session.log
# Run your commands
exit

Practical Examples and Best Practices

Complete Logging Script Example

Here’s a comprehensive example that combines multiple approaches:

bash
#!/bin/bash

# Enable debug mode with custom PS4 for better logging
PS4='[${BASH_SOURCE}:${LINENO}] '
set -x

# Create log file with timestamp
LOG_FILE="script_$(date +%Y%m%d_%H%M%S).log"
exec > >(tee -a "$LOG_FILE") 2>&1

echo "=== Starting Script Execution ==="
echo "Log file: $LOG_FILE"

# Example commands with variables
SOURCE_DIR="/var/www"
BACKUP_DIR="/backups/websites"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)

echo "Creating website backup..."
tar -czf "$BACKUP_DIR/website_$TIMESTAMP.tar.gz" -C "$SOURCE_DIR" .

echo "Backup completed successfully"
echo "=== Script Execution Complete ==="

Best Practices for Command Logging

  1. Use set -x at the script level for comprehensive logging
  2. Combine with custom PS4 for better traceability
  3. Redirect output to files for persistent logging
  4. Add timestamps to log entries for better tracking
  5. Use trap for automatic logging of specific events
  6. Consider performance impact - debug logging can slow down script execution
  7. Filter sensitive information from logs (passwords, API keys)

Performance Considerations

Debug logging can impact script performance, especially for loops and frequent operations. Use it strategically:

bash
#!/bin/bash

set -x

# Disable debug mode for performance-critical sections
set +x
for i in {1..1000}; do
    # Performance-critical operations
    echo "Processing item $i"
done
set -x  # Re-enable debug mode

# Continue with debugging
echo "Performance section completed"

For production scripts, consider making debug mode conditional:

bash
#!/bin/bash

# Enable debug mode if DEBUG environment variable is set
if [[ "${DEBUG:-}" == "1" ]]; then
    set -x
    echo "Debug mode enabled"
fi

# Rest of your script

Sources

  1. Shell Scripting Interview Questions for DevOps in 2025 - StarAgile
  2. Position Is Everything - How to Run Executable File in Linux

Conclusion

  • Use set -x as the primary method for echoing shell commands with variable expansion in your scripts
  • Combine with redirection (> filename or >> filename) to save logs persistently
  • Customize PS4 for better debug trace formatting with line numbers and timestamps
  • Consider performance impact and use conditional debug logging for production scripts
  • Alternative methods include bash -x, strace, and the script command for different logging needs

The set -x approach provides the most direct solution to your requirement of echoing commands with expanded variables, making it ideal for debugging and logging purposes in shell scripts.