Capturing Return Cod

To wait for all background processes and ensure they exit successfully, you can capture each exit code and check if any process failed.

Approach: Capture Exit Codes and Verify Success

#!/bin/bash

# Start process 1 in the background
long_running_command_1 &
pid1=$!

# Start process 2 in the background
long_running_command_2 &
pid2=$!

# Wait for both processes and capture their exit codes
wait "$pid1"
exit_code_1=$?

wait "$pid2"
exit_code_2=$?

# Check exit codes
if [[ $exit_code_1 -ne 0 || $exit_code_2 -ne 0 ]]; then
    echo "Error: One or more processes failed." >&2
    exit 1  # Fail the script if any process failed
fi

echo "All processes completed successfully."
exit 0

Explanation:

  1. Start each process in the background and capture its PID ($!).
  2. Use wait $pid to wait for each process and capture its exit code.
  3. Check exit codes:
    • If any exit code is non-zero, print an error and exit the script with exit 1.
    • If all are successful, print a success message and exit normally.

Alternative: Dynamic Handling for Multiple Processes

GT-Sandbox-Snapshot

Code

#!/bin/bash

# Function to simulate a long-running process
simulate_process() {
    sleep "$1"
    exit "$2"  # Simulates success (0) or failure (non-zero)
}

# Array to store process PIDs
pids=()

# Start multiple background processes with different durations and exit codes
simulate_process 2 0 & pids+=($!)  # Simulates success
simulate_process 3 1 & pids+=($!)  # Simulates failure
simulate_process 1 0 & pids+=($!)  # Simulates success

# Track overall success/failure
exit_status=0

# Wait for all processes and capture their exit codes
for pid in "${pids[@]}"; do
    wait "$pid"
    code=$?
    echo "Process $pid finished with exit code $code"

    if [[ $code -ne 0 ]]; then
        exit_status=1  # Mark as failure if any process fails
    fi
done

# Final exit based on process results
if [[ $exit_status -ne 0 ]]; then
    echo "Error: One or more processes failed." >&2
    exit 1
fi

echo "All processes completed successfully."
exit 0

Command to reproduce:

gt.sandbox.checkout.commit 593e717795f8e4e8d785 \
&& cd "${GT_SANDBOX_REPO}/bash" \
&& cmd.run.announce "./main.sh"

Recorded output of command:

Process 5932 finished with exit code 0
Process 5933 finished with exit code 1
Process 5934 finished with exit code 0
Error: One or more processes failed.
GT-Sandbox-Snapshot: with wait for PIDs function

Code

#!/bin/bash

###############################################################################
# simulate_process
# Simulates a long-running process.
#
# Arguments:
#   $1 - Duration to sleep (in seconds)
#   $2 - Exit code to simulate (0 for success, non-zero for failure)
#
# Exits:
#   Exits with the provided exit code.
###############################################################################
simulate_process() {
    sleep "$1"
    exit "$2"  # Simulates success (0) or failure (non-zero)
}

###############################################################################
# wait_for_processes
# Waits for all background processes specified by their PIDs.
#
# Arguments:
#   List of PIDs to wait for.
#
# Returns:
#   0 if all processes exit successfully, 1 if any process fails.
#
# Side Effects:
#   Prints the exit status of each process.
###############################################################################
wait_for_processes() {
    local pids=("$@")
    local exit_status=0

    for pid in "${pids[@]}"; do
        # Wait for the process to complete
        wait "$pid"
        local code=$?
        echo "Process $pid finished with exit code $code"

        # If any process fails, mark overall status as failure
        if [[ $code -ne 0 ]]; then
            exit_status=1
        fi
    done

    return $exit_status
}

# Array to store process PIDs
pids=()

# Start multiple background processes with different durations and exit codes
simulate_process 2 0 & pids+=("$!")  # Simulates success
simulate_process 3 1 & pids+=("$!")  # Simulates failure
simulate_process 1 0 & pids+=("$!")  # Simulates success

# Wait for all processes and check the overall exit status
wait_for_processes "${pids[@]}"
result=$?

if [[ $result -ne 0 ]]; then
    echo "Error: One or more processes failed." >&2
    exit 1
fi

echo "All processes completed successfully."
exit 0

Command to reproduce:

gt.sandbox.checkout.commit 3fed59b2dc997683b5c4 \
&& cd "${GT_SANDBOX_REPO}/bash" \
&& cmd.run.announce "./main.sh"

Recorded output of command:

Process 6920 finished with exit code 0
Process 6921 finished with exit code 1
Process 6924 finished with exit code 0
Error: One or more processes failed.
Appears to work OK with interrupting
SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON=5
START_MILLIS=$(date +%s%3N)

echo_log() {
  local millis_elapsed_since_start=$(( $(date +%s%3N) - START_MILLIS ))

  echo_dim "[$BASHPID][elapsed: ${millis_elapsed_since_start:?}] ${*}"
}
export -f echo_log

###############################################################################
# simulate_process
# Simulates a long-running process.
#
# Arguments:
#   $1 - Duration to sleep (in seconds)
#   $2 - Exit code to simulate (0 for success, non-zero for failure)
#
# Exits:
#   Exits with the provided exit code.
###############################################################################
simulate_process() {

  local seconds_to_sleep="${1:?seconds_to_sleep}"
  echo_log "simulate_process that will sleep for ${seconds_to_sleep:?} seconds: \$\$ = $$ (parent), \$BASHPID = $BASHPID (child)"

  sleep "${seconds_to_sleep:?}"
  if [[ "${seconds_to_sleep:?}" == "${SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON:?}" ]]; then
      echo_log "Enough of sleeping, let's throw an error"
      throw "throw on sleep of ${SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON:?} on purpose"
  fi
  exit "$2"  # Simulates success (0) or failure (non-zero)
}

###############################################################################
# wait_for_processes
# Waits for all background processes specified by their PIDs.
#
# Arguments:
#   List of PIDs to wait for.
#
# Returns:
#   0 if all processes exit successfully, 1 if any process fails.
#
# Side Effects:
#   Prints the exit status of each process.
###############################################################################
wait_for_processes() {
    local pids=("$@")
    local exit_status=0

    for pid in "${pids[@]}"; do
        # Wait for the process to complete
        wait "$pid"
        local code=$?
        echo_log "Process $pid finished with exit code $code"

        # If any process fails, mark overall status as failure
        if [[ $code -ne 0 ]]; then
            exit_status=1
        fi
    done

    return $exit_status
}

main() {
  echo_log "Starting main script: \$\$ = $$, \$BASHPID = $BASHPID"
  # Array to store process PIDs
  pids=()

  # Start multiple background processes with different durations and exit codes
  simulate_process 1 0 & pids+=("$!")  # Simulates success
  simulate_process 2 0 & pids+=("$!")  # Simulates success
  simulate_process ${SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON:?} 0 & pids+=("$!")  # Simulates failure

  # Wait for all processes and check the overall exit status
  wait_for_processes "${pids[@]}"
  result=$?

  if [[ $result -ne 0 ]]; then
      echo_log "Error: One or more processes failed." >&2
      exit 1
  fi

  echo_log "All processes completed successfully."
  exit 0
}

main "${@}" || exit 1

Snapshot:

* 9405c20 - (0 seconds ago) [2025-01-31T22-10-16PST] Modified files: main.sh - nickolaykondratyev (HEAD -> bash)
GT-Sandbox-Snapshot: Final snapshot of playing with waiting for processes to complete.

Code

SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON=5
START_MILLIS=$(date +%s%3N)

echo_log() {
  local millis_elapsed_since_start=$(( $(date +%s%3N) - START_MILLIS ))

  echo_dim "[$BASHPID][elapsed: ${millis_elapsed_since_start:?}] ${*}"
}
export -f echo_log

###############################################################################
# simulate_process
# Simulates a long-running process.
#
# Arguments:
#   $1 - Duration to sleep (in seconds)
#   $2 - Exit code to simulate (0 for success, non-zero for failure)
#
# Exits:
#   Exits with the provided exit code.
###############################################################################
simulate_process() {

  local seconds_to_sleep="${1:?seconds_to_sleep}"
  echo_log "simulate_process that will sleep for ${seconds_to_sleep:?} seconds, and exit with [${2}]: \$\$ = $$ (parent), \$BASHPID = $BASHPID (child)"

  sleep "${seconds_to_sleep:?}"
  if [[ "${seconds_to_sleep:?}" == "${SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON:?}" ]]; then
      echo_log "Enough of sleeping, let's throw an error"
      throw "throw on sleep of ${SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON:?} on purpose"
  fi
  exit "$2"  # Simulates success (0) or failure (non-zero)
}


###############################################################################
# wait_for_processes
# Waits for all background processes specified by their PIDs.
#
# Example code:
# ```
#  # Array to store process PIDs
#  pids=()
#
#  # Start multiple background processes with different durations and exit codes
#  simulate_process 1 0 & pids+=("$!")  # Simulates success
#  simulate_process 2 0 & pids+=("$!")  # Simulates success
#  simulate_process 3 1 & pids+=("$!")  # Simulates success
#
#  # Wait for all processes and check the overall exit status
#  if wait_for_processes "${pids[@]}"; then
#    echo.green "All processes completed successfully."
#  else
#    echo.red "Error: One or more processes failed." >&2
#    exit 1
#  fi
# ```
# --------------------------------------------------------------------------------
# Arguments:
#   List of PIDs to wait for.
#
# Returns:
#   0 if all processes exit successfully, 1 if any process fails.
#
# Side Effects:
#   Prints the exit status of each process.
#
###############################################################################
wait_for_processes() {
    local pids=("$@")
    local exit_status=0

    for pid in "${pids[@]}"; do
        echo_log "Waiting for process $pid to complete"
        # Wait for the process to complete
        wait "$pid"
        local code=$?
        echo_log "Process $pid finished with exit code $code"

        # If any process fails, mark overall status as failure
        if [[ $code -ne 0 ]]; then
            exit_status=1
        fi
    done

    return $exit_status
}

main() {
  echo_log "Starting main script: \$\$ = $$, \$BASHPID = $BASHPID"
  # Array to store process PIDs
  pids=()

  # Start multiple background processes with different durations and exit codes
  simulate_process 1 0 & pids+=("$!")  # Simulates success
  simulate_process 2 0 & pids+=("$!")  # Simulates success
  simulate_process 3 1 & pids+=("$!")  # Simulates success
  # simulate_process ${SLEEP_DELAY_THAT_WE_WANT_TO_THROW_ON:?} 0 & pids+=("$!")  # Simulates failure

  # Wait for all processes and check the overall exit status
  if wait_for_processes "${pids[@]}"; then
    echo.green "All processes completed successfully."
  else
    echo.red "Error: One or more processes failed." >&2
    exit 1
  fi
}

main "${@}" || exit 1

Command to reproduce:

gt.sandbox.checkout.commit 6337812d19525a437b28 \
&& cd "${GT_SANDBOX_REPO}/bash" \
&& cmd.run.announce "./main.sh"

Recorded output of command:

[11649][elapsed: 6] Starting main script: $$ = 11649, $BASHPID = 11649
[11652][elapsed: 13] simulate_process that will sleep for 1 seconds, and exit with [0]: $$ = 11649 (parent), $BASHPID = 11652 (child)
[11655][elapsed: 14] simulate_process that will sleep for 3 seconds, and exit with [1]: $$ = 11649 (parent), $BASHPID = 11655 (child)
[11653][elapsed: 14] simulate_process that will sleep for 2 seconds, and exit with [0]: $$ = 11649 (parent), $BASHPID = 11653 (child)
[11649][elapsed: 14] Waiting for process 11652 to complete
[11649][elapsed: 1037] Process 11652 finished with exit code 0
[11649][elapsed: 1046] Waiting for process 11653 to complete
[11649][elapsed: 2036] Process 11653 finished with exit code 0
[11649][elapsed: 2046] Waiting for process 11655 to complete
[11649][elapsed: 3038] Process 11655 finished with exit code 1
Error: One or more processes failed.


Children
  1. Dynamic Capture Example