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?
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
- Variable Expansion in Debug Mode
- Logging Commands to Files
- Alternative Approaches
- Practical Examples and Best Practices
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.
#!/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:
#!/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.
#!/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:
#!/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:
#!/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:
#!/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 -x myscript.sh
Or run it and save to a file:
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:
#!/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):
strace -f -o strace.log bash myscript.sh
Using script Command for Session Recording
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:
#!/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
- Use
set -xat the script level for comprehensive logging - Combine with custom PS4 for better traceability
- Redirect output to files for persistent logging
- Add timestamps to log entries for better tracking
- Use
trapfor automatic logging of specific events - Consider performance impact - debug logging can slow down script execution
- 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:
#!/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:
#!/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
- Shell Scripting Interview Questions for DevOps in 2025 - StarAgile
- Position Is Everything - How to Run Executable File in Linux
Conclusion
- Use
set -xas the primary method for echoing shell commands with variable expansion in your scripts - Combine with redirection (
> filenameor>> filename) to save logs persistently - Customize
PS4for 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 thescriptcommand 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.