Linux Command Reference

exec

Synopsis

exec [-cl] [-a name] [command [arguments]]

Description

The exec command has two primary uses in Unix/Linux shells. First, when given a command as an argument, it replaces the current shell process with the specified command - the new program inherits the process ID (PID) and takes over completely. When exec completes, there is no shell to return to because it has been replaced. This is a fundamental difference from normal command execution where the shell creates a child process.

Second, when used without a command, exec can manipulate file descriptors in the current shell, redirecting standard input, output, and error streams. This allows for sophisticated I/O redirection that persists for the life of the shell or script. These two modes make exec essential for shell script optimization, container entry points, logging systems, and process management.

Key Concepts

Detailed Examples

Example 1

Basic Process Replacement

Demonstrate the fundamental behavior of exec - replacing the current shell with a new command. This shows how exec differs from normal command execution and why it doesn't return.
# First, note your current shell PID echo "Current shell PID: $$" echo "Current shell: $0" # Now exec will replace this shell with 'top' # After this command, the shell is GONE - replaced by top # You won't return to this shell exec top # THIS LINE WILL NEVER EXECUTE echo "You will never see this message" # To demonstrate safely, we'll use a wrapper: # Create a subscript to show replacement cat > /tmp/test_exec.sh << 'EOF' #!/bin/bash echo "Script PID before exec: $$" echo "About to exec ls command..." exec ls -la /tmp echo "This line will never execute" EOF chmod +x /tmp/test_exec.sh /tmp/test_exec.sh # The script shows ls output but never prints the last echo
Output:
Script PID before exec: 12345 About to exec ls command... total 48 drwxrwxrwt 12 root root 4096 Dec 14 14:30 . drwxr-xr-x 20 root root 4096 Nov 28 09:12 .. -rwxr-xr-x 1 user user 123 Dec 14 14:30 test_exec.sh # The script exits after ls - no return to the script
Warning:

Never run 'exec command' directly in your interactive shell unless you want to replace your shell with that command. When the command finishes, your terminal session will close!

Example 2

Optimize Script Performance with exec

Use exec at the end of wrapper scripts to avoid unnecessary process overhead. When a script's only job is to set up environment and launch another program, exec eliminates the wrapper process after setup is complete.
# Create an application wrapper script cat > /usr/local/bin/myapp_launcher.sh << 'EOF' #!/bin/bash # Application launcher with environment setup # Set up environment export APP_HOME="/opt/myapp" export APP_CONFIG="$APP_HOME/config/production.conf" export JAVA_OPTS="-Xmx2048m -Xms1024m" export LOG_DIR="/var/log/myapp" # Ensure log directory exists mkdir -p "$LOG_DIR" # Set working directory cd "$APP_HOME" # Log startup echo "[$(date)] Starting application" >> "$LOG_DIR/startup.log" # Replace this shell with the actual application # The wrapper process will be replaced, saving memory exec java $JAVA_OPTS -jar "$APP_HOME/myapp.jar" --config="$APP_CONFIG" # Never reached EOF chmod +x /usr/local/bin/myapp_launcher.sh # Compare process trees: # WITHOUT exec (wasteful): # ├─myapp_launcher.sh (PID 1000) # └─java -jar myapp.jar (PID 1001) # Two processes, wrapper sits idle consuming resources # WITH exec (efficient): # └─java -jar myapp.jar (PID 1000) # One process, wrapper is replaced # Run the launcher /usr/local/bin/myapp_launcher.sh
Output:
# In startup.log: [Sun Dec 14 14:35:23 EST 2025] Starting application # Process list shows only java, not the wrapper: $ ps aux | grep myapp user 1000 2.5 15.2 3145728 1048576 ? Sl 14:35 0:15 java -Xmx2048m -Xms1024m -jar /opt/myapp/myapp.jar
Note:

This pattern is especially important for init systems, Docker containers, and systemd services. The wrapper does its job (setup) then gets out of the way by replacing itself with the actual application.

Example 3

Redirect Output for Entire Script

Use exec to redirect output streams for an entire script without modifying every command. This is cleaner than redirecting each individual command and essential for comprehensive logging.
# Create a script with comprehensive logging cat > /tmp/logging_script.sh << 'EOF' #!/bin/bash # Redirect all output (stdout) to a log file # All subsequent echo, printf, etc. go to the log exec > /var/log/myscript.log # Redirect all errors (stderr) to an error log exec 2> /var/log/myscript_errors.log # Or combine both to same file: # exec &> /var/log/myscript_all.log echo "Script started at $(date)" echo "Running as user: $USER" # Simulate some work for i in {1..5}; do echo "Processing item $i" sleep 1 done # This goes to stderr (error log) echo "Warning: This is a test warning" >&2 echo "Script completed at $(date)" # All output went to files, not terminal EOF chmod +x /tmp/logging_script.sh /tmp/logging_script.sh # Check the logs echo "=== Standard Output Log ===" cat /var/log/myscript.log echo "=== Error Log ===" cat /var/log/myscript_errors.log
Output:
=== Standard Output Log === Script started at Sun Dec 14 14:40:15 EST 2025 Running as user: craig Processing item 1 Processing item 2 Processing item 3 Processing item 4 Processing item 5 Script completed at Sun Dec 14 14:40:20 EST 2025 === Error Log === Warning: This is a test warning
Note:

This technique is much cleaner than appending '>> logfile 2>&1' to every command. The exec redirection applies to all subsequent commands in the script automatically.

Example 4

Dual Logging (Screen and File)

Combine exec with process substitution and tee to send output to both the screen and a log file simultaneously. This is useful for interactive scripts where you want to see output but also keep a record.
# Create script with dual logging cat > /tmp/dual_log.sh << 'EOF' #!/bin/bash LOG_FILE="/var/log/deployment_$(date +%Y%m%d_%H%M%S).log" # Redirect stdout and stderr to both screen and log file exec > >(tee -a "$LOG_FILE") exec 2>&1 echo "======================================" echo "Deployment Script Started" echo "Time: $(date)" echo "Log file: $LOG_FILE" echo "======================================" deploy_step() { local step_name="$1" echo "" echo "[$(date +%H:%M:%S)] Step: $step_name" echo "----------------------------------------" } deploy_step "Stopping application" # systemctl stop myapp echo "Application stopped successfully" deploy_step "Backing up current version" # tar czf backup_$(date +%Y%m%d).tar.gz /opt/myapp echo "Backup completed" deploy_step "Deploying new version" # cp new_version.jar /opt/myapp/ echo "New version deployed" deploy_step "Starting application" # systemctl start myapp echo "Application started successfully" echo "" echo "======================================" echo "Deployment completed at $(date)" echo "Check log file for details: $LOG_FILE" echo "======================================" EOF chmod +x /tmp/dual_log.sh /tmp/dual_log.sh # Output appears on screen AND is saved to log file
Output (appears on screen AND in log file):
====================================== Deployment Script Started Time: Sun Dec 14 14:45:30 EST 2025 Log file: /var/log/deployment_20251214_144530.log ====================================== [14:45:30] Step: Stopping application ---------------------------------------- Application stopped successfully [14:45:30] Step: Backing up current version ---------------------------------------- Backup completed [14:45:31] Step: Deploying new version ---------------------------------------- New version deployed [14:45:31] Step: Starting application ---------------------------------------- Application started successfully ====================================== Deployment completed at Sun Dec 14 14:45:32 EST 2025 Check log file for details: /var/log/deployment_20251214_144530.log ======================================
Note:

The >(tee -a "$LOG_FILE") syntax uses process substitution to create a pipe to tee, which writes to both stdout (screen) and the log file. This is perfect for deployment scripts and interactive system administration tasks.

Example 5

Save and Restore File Descriptors

Temporarily redirect output to a file, then restore original output destination. This advanced technique allows you to control where output goes at different points in a script.
# Create script demonstrating FD save/restore cat > /tmp/fd_management.sh << 'EOF' #!/bin/bash echo "This goes to normal stdout (your screen)" # Save current stdout to file descriptor 3 exec 3>&1 # Redirect stdout to a file exec 1>/tmp/redirected_output.txt echo "This goes to /tmp/redirected_output.txt" echo "So does this" echo "Current time: $(date)" # Restore stdout from file descriptor 3 exec 1>&3 # Close file descriptor 3 (cleanup) exec 3>&- echo "Back to normal stdout (your screen)" echo "Check the file:" cat /tmp/redirected_output.txt # Advanced: Save both stdout and stderr echo "=== Advanced FD Management ===" # Save current stdout to FD 6 exec 6>&1 # Save current stderr to FD 7 exec 7>&2 # Redirect both to a combined log exec &> /tmp/combined.log echo "This goes to combined.log" echo "Error message" >&2 # Restore original stdout exec 1>&6 6>&- # Restore original stderr exec 2>&7 7>&- echo "Back to screen" echo "Combined log contents:" cat /tmp/combined.log EOF chmod +x /tmp/fd_management.sh /tmp/fd_management.sh
Output:
This goes to normal stdout (your screen) Back to normal stdout (your screen) Check the file: This goes to /tmp/redirected_output.txt So does this Current time: Sun Dec 14 14:50:15 EST 2025 === Advanced FD Management === Back to screen Combined log contents: This goes to combined.log Error message
Note:

This technique uses file descriptor (FD) manipulation to save the original stdout to FD 3, redirect stdout to a file, then restore it later. FDs 0, 1, 2 are stdin, stdout, stderr. FDs 3-9 are available for custom use.

Example 6

Docker/Container Entry Point Pattern

The standard pattern for Docker container entry points uses exec to replace the shell with the main application. This ensures the application receives signals properly and becomes PID 1 in the container.
# Create a Docker-style entry point script cat > /usr/local/bin/docker-entrypoint.sh << 'EOF' #!/bin/bash set -e # Function to handle setup setup_environment() { echo "Setting up environment..." # Create necessary directories mkdir -p /var/log/app mkdir -p /var/run/app # Set permissions chown -R app:app /var/log/app /var/run/app # Generate config from environment variables cat > /etc/app/config.yml << EOFCONFIG database: host: ${DB_HOST:-localhost} port: ${DB_PORT:-5432} name: ${DB_NAME:-myapp} logging: level: ${LOG_LEVEL:-INFO} file: /var/log/app/application.log EOFCONFIG echo "Environment setup complete" } # Run setup setup_environment # Log startup echo "[$(date)] Starting application as PID $$" # Handle different commands case "$1" in bash|sh|/bin/bash|/bin/sh) # If user wants a shell, give them one echo "Starting interactive shell..." exec "$@" ;; "") # No command specified, run default application echo "Running default application..." exec /usr/local/bin/myapp ;; *) # Run the specified command echo "Running: $@" exec "$@" ;; esac # Never reached EOF chmod +x /usr/local/bin/docker-entrypoint.sh # In Dockerfile, this would be: # ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] # CMD ["myapp"] # The exec ensures: # 1. myapp becomes PID 1 (receives signals directly) # 2. No orphaned shell process consuming resources # 3. Proper signal handling for graceful shutdown
Container startup output:
Setting up environment... Environment setup complete [Sun Dec 14 14:55:00 EST 2025] Starting application as PID 1 Running default application... # Inside container: $ ps aux USER PID COMMAND app 1 /usr/local/bin/myapp # Note: PID 1 is myapp, not the shell script
Note:

Using exec in container entry points is critical. Without it, the shell script remains as PID 1 and your application doesn't receive signals (like SIGTERM) properly, breaking graceful shutdown. This is a Docker best practice.

Example 7

Build a Command Wrapper with Logging

Create a generic command wrapper that logs all output while executing the actual command. This pattern is useful for auditing, debugging, or compliance requirements.
# Create a universal command wrapper cat > /usr/local/bin/logged-exec << 'EOF' #!/bin/bash # Wrapper that logs command execution with timestamps if [ $# -eq 0 ]; then echo "Usage: logged-exec <command> [args...]" exit 1 fi # Configuration LOG_DIR="/var/log/command-execution" mkdir -p "$LOG_DIR" # Generate log filename from command name COMMAND_NAME=$(basename "$1") TIMESTAMP=$(date +%Y%m%d_%H%M%S) LOG_FILE="$LOG_DIR/${COMMAND_NAME}_${TIMESTAMP}.log" # Log execution details { echo "=========================================" echo "Command Execution Log" echo "=========================================" echo "Timestamp: $(date)" echo "User: $USER" echo "Working Directory: $PWD" echo "Command: $*" echo "PID: $$" echo "=========================================" echo "" } > "$LOG_FILE" # Save original file descriptors exec 3>&1 4>&2 # Redirect stdout and stderr to log file (appending) exec 1>> "$LOG_FILE" 2>&1 # Print start marker echo "[START: $(date)]" # Execute the actual command # Note: We use "$@" to preserve arguments properly "$@" EXIT_CODE=$? # Print end marker echo "[END: $(date)] Exit code: $EXIT_CODE" # Restore original file descriptors exec 1>&3 2>&4 # Close the saved descriptors exec 3>&- 4>&- # Print summary to screen echo "Command executed: $*" echo "Exit code: $EXIT_CODE" echo "Log saved to: $LOG_FILE" exit $EXIT_CODE EOF chmod +x /usr/local/bin/logged-exec # Use the wrapper logged-exec ls -la /etc logged-exec grep -r "error" /var/log logged-exec python3 my_script.py --verbose # All executions are logged with full output
Output on screen:
Command executed: ls -la /etc Exit code: 0 Log saved to: /var/log/command-execution/ls_20251214_150000.log # In the log file: ========================================= Command Execution Log ========================================= Timestamp: Sun Dec 14 15:00:00 EST 2025 User: craig Working Directory: /home/craig Command: ls -la /etc PID: 12345 ========================================= [START: Sun Dec 14 15:00:00 EST 2025] total 1234 drwxr-xr-x 142 root root 12288 Dec 14 14:00 . drwxr-xr-x 20 root root 4096 Nov 28 09:12 .. drwxr-xr-x 3 root root 4096 Oct 15 10:23 acpi ... [END: Sun Dec 14 15:00:00 EST 2025] Exit code: 0
Note:

This wrapper doesn't use exec to replace itself because it needs to capture output and provide summary information. However, it demonstrates sophisticated FD management with exec for redirection.

Example 8

Exec with Signal Handling

Demonstrate how exec affects signal handling. When you exec, the new process inherits signal handlers, which is important for building robust wrapper scripts that need to handle shutdown signals.
# Create a signal-aware wrapper cat > /tmp/signal_wrapper.sh << 'EOF' #!/bin/bash # Flag to track if we're shutting down SHUTTING_DOWN=false # Cleanup function cleanup() { echo "" echo "Received shutdown signal..." SHUTTING_DOWN=true # If we haven't exec'd yet, do cleanup if [ "$EXEC_DONE" != "true" ]; then echo "Performing cleanup..." # Clean up temp files, close connections, etc. echo "Cleanup complete" fi exit 0 } # Set up signal handlers trap cleanup SIGTERM SIGINT echo "Wrapper script starting (PID: $$)" echo "Press Ctrl+C to test signal handling" # Do some setup work echo "Performing initialization..." sleep 2 if [ "$SHUTTING_DOWN" = true ]; then echo "Interrupted during setup" exit 1 fi echo "Setup complete" # At this point, we're ready to exec # After exec, this script is replaced and trap handlers are gone # The new process gets default signal handling export EXEC_DONE=true echo "About to exec the main application..." echo "PID will remain $$, but this script will be replaced" # In real usage, you'd exec the actual application here # For demonstration, we'll exec a long-running command exec sleep 30 # Never reached echo "This line never executes" EOF chmod +x /tmp/signal_wrapper.sh # Run it and try Ctrl+C before and after exec /tmp/signal_wrapper.sh # Before exec: trap handler catches signal # After exec: sleep catches signal (default behavior)
Output (if Ctrl+C pressed before exec):
Wrapper script starting (PID: 12345) Press Ctrl+C to test signal handling Performing initialization... ^C Received shutdown signal... Performing cleanup... Cleanup complete # Output (if Ctrl+C pressed after exec): Wrapper script starting (PID: 12345) Press Ctrl+C to test signal handling Performing initialization... Setup complete About to exec the main application... PID will remain 12345, but this script will be replaced ^C # Sleep terminates (default behavior, no custom cleanup)
Warning:

When you exec, your shell script's trap handlers are lost. The new process gets default signal handling. If you need custom signal handling in the final application, implement it in that application, not in the wrapper script.

Example 9

Exec with Environment Modification

Use exec -c to clear the environment, or -a to change the command name ($0). These options are useful for security, testing, and creating clean execution environments.
# Demonstrate exec options # 1. Change the apparent command name with -a cat > /tmp/test_name.sh << 'EOF' #!/bin/bash echo "My name is: $0" EOF chmod +x /tmp/test_name.sh # Normal execution /tmp/test_name.sh # Output: My name is: /tmp/test_name.sh # With exec -a to change $0 cat > /tmp/wrapper.sh << 'EOF' #!/bin/bash echo "Wrapper starting, my name is: $0" # Execute test_name.sh but make it think its name is "custom_name" exec -a "custom_name" /tmp/test_name.sh EOF chmod +x /tmp/wrapper.sh /tmp/wrapper.sh # Output: # Wrapper starting, my name is: /tmp/wrapper.sh # My name is: custom_name # 2. Clear environment with -c (security/isolation) cat > /tmp/clean_env.sh << 'EOF' #!/bin/bash echo "=== Current Environment ===" export SENSITIVE_DATA="secret123" export API_KEY="key456" env | sort echo "" echo "=== Running with clean environment ===" # -c clears all environment variables # -l makes it a login shell (reads profile files) exec -c env | sort # Never reached EOF chmod +x /tmp/clean_env.sh /tmp/clean_env.sh # 3. Practical use: run command in minimal environment cat > /tmp/secure_exec.sh << 'EOF' #!/bin/bash # Run a command in a minimal, controlled environment # Useful for security, testing, or avoiding environment pollution # Build a minimal environment CLEAN_PATH="/usr/local/bin:/usr/bin:/bin" CLEAN_HOME="/tmp/clean_home" mkdir -p "$CLEAN_HOME" # Execute with only specified variables exec -c env -i \ PATH="$CLEAN_PATH" \ HOME="$CLEAN_HOME" \ TERM="$TERM" \ "$@" EOF chmod +x /tmp/secure_exec.sh # Run a command in clean environment /tmp/secure_exec.sh bash -c 'echo "PATH=$PATH"; echo "HOME=$HOME"; env | wc -l'
Output:
=== Current Environment === API_KEY=key456 HOME=/home/craig PATH=/usr/local/bin:/usr/bin:/bin SENSITIVE_DATA=secret123 ...many more variables... === Running with clean environment === PWD=/tmp SHLVL=1 _=/usr/bin/env # Secure exec output: PATH=/usr/local/bin:/usr/bin:/bin HOME=/tmp/clean_home 3 # Only 3 environment variables instead of dozens
Note:

The -c flag is powerful for security: it prevents environment variable leakage to subprocess. The -a flag is useful for process name spoofing in ps output, which can be useful for daemon management and security obscurity.

Example 10

Complete Production Service Starter

A comprehensive example combining everything: environment setup, logging, error handling, signal management, and process replacement. This serves as a production-ready template for service wrapper scripts.
#!/bin/bash # File: /usr/local/bin/service-starter # Production-ready service wrapper with comprehensive features set -e # Exit on error set -u # Exit on undefined variable set -o pipefail # Exit on pipe failure # ============================================ # Configuration # ============================================ SERVICE_NAME="${SERVICE_NAME:-myapp}" SERVICE_USER="${SERVICE_USER:-app}" SERVICE_HOME="${SERVICE_HOME:-/opt/$SERVICE_NAME}" LOG_DIR="${LOG_DIR:-/var/log/$SERVICE_NAME}" PID_FILE="${PID_FILE:-/var/run/$SERVICE_NAME.pid}" CONFIG_DIR="${CONFIG_DIR:-/etc/$SERVICE_NAME}" # ============================================ # Logging Setup # ============================================ TIMESTAMP=$(date +%Y%m%d_%H%M%S) LOG_FILE="$LOG_DIR/${SERVICE_NAME}_${TIMESTAMP}.log" # Ensure log directory exists mkdir -p "$LOG_DIR" # Dual logging function (before exec) log() { local level="$1" shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE" } # ============================================ # Error Handling # ============================================ error_exit() { log "ERROR" "$1" exit 1 } # ============================================ # Validation # ============================================ validate_environment() { log "INFO" "Validating environment..." # Check if running as correct user if [ "$EUID" -eq 0 ] && [ "$SERVICE_USER" != "root" ]; then error_exit "This service should not run as root. Switch to $SERVICE_USER first." fi # Check required directories [ -d "$SERVICE_HOME" ] || error_exit "Service home not found: $SERVICE_HOME" [ -d "$CONFIG_DIR" ] || error_exit "Config directory not found: $CONFIG_DIR" # Check required files [ -f "$SERVICE_HOME/bin/application.jar" ] || error_exit "Application jar not found" log "INFO" "Environment validation passed" } # ============================================ # Environment Setup # ============================================ setup_environment() { log "INFO" "Setting up environment..." # Set working directory cd "$SERVICE_HOME" || error_exit "Cannot change to service home" # Export environment variables export JAVA_HOME="${JAVA_HOME:-/usr/lib/jvm/java-11-openjdk}" export PATH="$JAVA_HOME/bin:$PATH" export APP_CONFIG="$CONFIG_DIR/application.conf" export LOG_DIR # JVM options export JAVA_OPTS="-Xmx2048m -Xms1024m" export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC" export JAVA_OPTS="$JAVA_OPTS -Dlog.dir=$LOG_DIR" export JAVA_OPTS="$JAVA_OPTS -Dconfig.file=$APP_CONFIG" log "INFO" "Environment setup complete" } # ============================================ # Pre-flight Checks # ============================================ preflight_checks() { log "INFO" "Running preflight checks..." # Check if already running if [ -f "$PID_FILE" ]; then OLD_PID=$(cat "$PID_FILE") if kill -0 "$OLD_PID" 2>/dev/null; then error_exit "Service already running with PID $OLD_PID" else log "WARN" "Removing stale PID file" rm -f "$PID_FILE" fi fi # Check port availability (if applicable) if netstat -tuln | grep -q ":8080 "; then error_exit "Port 8080 already in use" fi # Check disk space AVAILABLE_SPACE=$(df -BG "$LOG_DIR" | awk 'NR==2 {print $4}' | sed 's/G//') if [ "$AVAILABLE_SPACE" -lt 1 ]; then error_exit "Insufficient disk space in $LOG_DIR" fi log "INFO" "Preflight checks passed" } # ============================================ # Main Execution # ============================================ main() { log "INFO" "=========================================" log "INFO" "Starting $SERVICE_NAME service" log "INFO" "PID: $$" log "INFO" "User: $(whoami)" log "INFO" "=========================================" # Run validation and setup validate_environment setup_environment preflight_checks # Write PID file echo $$ > "$PID_FILE" log "INFO" "PID file created: $PID_FILE" # Setup log rotation for this session # (Keep last 10 log files) find "$LOG_DIR" -name "${SERVICE_NAME}_*.log" -type f | \ sort -r | tail -n +11 | xargs rm -f 2>/dev/null || true # Set up file descriptor redirection for the exec'd process # All further output goes to log file exec 1>> "$LOG_FILE" 2>&1 log "INFO" "Executing application..." log "INFO" "Command: java $JAVA_OPTS -jar $SERVICE_HOME/bin/application.jar" # Replace this script with the actual application # PID remains the same, script is replaced exec java $JAVA_OPTS -jar "$SERVICE_HOME/bin/application.jar" # Never reached error_exit "exec failed - this line should never execute" } # ============================================ # Entry Point # ============================================ main "$@"
Output:
[2025-12-14 15:15:00] [INFO] ========================================= [2025-12-14 15:15:00] [INFO] Starting myapp service [2025-12-14 15:15:00] [INFO] PID: 12345 [2025-12-14 15:15:00] [INFO] User: app [2025-12-14 15:15:00] [INFO] ========================================= [2025-12-14 15:15:00] [INFO] Validating environment... [2025-12-14 15:15:00] [INFO] Environment validation passed [2025-12-14 15:15:00] [INFO] Setting up environment... [2025-12-14 15:15:00] [INFO] Environment setup complete [2025-12-14 15:15:00] [INFO] Running preflight checks... [2025-12-14 15:15:00] [INFO] Preflight checks passed [2025-12-14 15:15:00] [INFO] PID file created: /var/run/myapp.pid [2025-12-14 15:15:00] [INFO] Executing application... [2025-12-14 15:15:00] [INFO] Command: java -Xmx2048m -Xms1024m... # Process list shows Java, not the wrapper: $ ps aux | grep 12345 app 12345 ... java -Xmx2048m ... application.jar
Note:

This production template demonstrates best practices: validation before execution, comprehensive logging, error handling, PID file management, preflight checks, and finally exec to replace the wrapper with the actual application. Use this as a starting point for production service wrappers.

Tips & Best Practices

Use Exec in Container Entry Points
Always use exec in Docker/container entry point scripts. This ensures your application becomes PID 1 and receives signals properly. Without exec, the shell script remains PID 1 and signal handling breaks.
Never Exec in Interactive Shell
Don't run exec command directly in your interactive terminal unless you want to replace your shell with that command. When the command finishes, your terminal closes. Always test exec commands in scripts first.
Use Exec for Script Optimization
If a script's only purpose is setup followed by running another program, use exec on the final command. This eliminates the wrapper process, saving memory. Pattern: setup → exec application.
File Descriptor Management
When using exec for I/O redirection, save original descriptors if you need to restore them: exec 3>&1; exec 1>logfile; ...; exec 1>&3 3>&-. This allows temporary redirection.
Understand Signal Implications
After exec, your script's trap handlers are gone. The new process uses default signal handling. If you need custom signal handling, implement it in the final application, not the wrapper.
Check Exec Return Value
If exec fails (command not found, not executable), it returns to the shell with exit code 126-127. Handle this: exec command || { echo "exec failed"; exit 1; }
Use -c for Security
The exec -c flag clears the environment, preventing variable leakage. Useful for security-sensitive applications. Combine with env -i to set a minimal, controlled environment.
PID Preservation
When exec replaces the process, the PID stays the same. This is important for PID files and process monitoring. The process ID doesn't change, only the program running with that PID.
Combine with Logging
Use exec > >(tee logfile) for dual logging (screen and file). Or redirect all output to a log before exec: exec &>>logfile; exec application. Plan your logging strategy before exec.