I've figured out that I'm able to complete my current task without needing to have the sub-sub-routine kill the program. My particular sub-sub-routine can, in my application, return a success/fail exit code and I can use its parent function to terminate the program using the original workaround if needed.

So I've got my situation sorted, but for future reference and knowledge, I'm wondering how to leverage the original workaround to do the same thing in the sub-sub-routine. Perhaps if I understood the syntax, I could figure it out. Here's what I see so far.

The workaround code (from here) is:
Code:
# Top of the main script body
trap "exit 1" TERM
export TOP_PID=$$

# Function definition
TestForBadProblem()
{
  if "$badProblem" = true
  then
    kill -s TERM $TOP_PID
  fi
  returnData="Some_Data"
  echo $returnData
}

# Main program code body
returnedData=$( TestForBadProblem )


All right, taking it line by line...

Code:
trap "exit 1" TERM

It appears as though it's setting up an interrupt, where, if it received the signal "TERM" it will execute the code "exit 1" in the context of the script that defined the trap.

Code:
export TOP_PID=$$

Create (or perhaps update?) an environment variable named TOP_PID and set it to the process ID of the current script.

Code:
kill -s TERM $TOP_PID

This looks like it's trying to kill the top-level script process, specifying the signal spec "TERM" so that the trap handler catches it, and specifying the process ID of the top-level script process, by using the environment variable which contains the top level script's PID.

Based on what I see, the method should have worked. If I add a sub-sub routine that contains the line "kill -s TERM $TOP_PID" it should kill the top level script. That's not what's happening though. What seems to happen is that the parent function completes what it's doing and only then the script terminates.

Here is the code and output from my updated example script.

Code:
Code:
#!/bin/bash

# ----------------------------------------------
# Bash - How to fully exit a program from within
# a function which returns data to the caller.
# ----------------------------------------------
# Program to demonstrate an issue with Bash.
# The problem is: You want to write a function
# which can either:
#   - Return data to the caller.
#   - Or exit the script completely if it
#     discovers some kind of a problem.
# The problem is that Bash can't do both. You can
# only exit the script if you are at the top level
# or from inside a function where the call to the
# function doesn't try to retrieve data from it.
# You can't do variable=$(function) where the
# function wants to maybe exit the script.
# This program demonstrates the problem and the
# work-around.

# ----------------------------------------------
# Work-around is here
# ----------------------------------------------
# These two lines are part of a work-around to the
# problem that this program demonstrates. This
# was obtained from the following StackOverflow
# question:
# https://stackoverflow.com/questions/9893667/is-there-a-way-to-write-a-bash-function-which-aborts-the-whole-execution-no-mat
trap "exit 1" TERM
export TOP_PID=$$
# Combine it with "kill -s TERM $TOP_PID" inside
# the function where you want to exit the program. 


# ----------------------------------------------
# Function: Test for a problem. If there is no
# problem, then gather some data and return it.
# If there is a problem, exit the program.
# ----------------------------------------------
TestForBadProblem()
{
  echo "Testing for a potential bad problem..."  >&2
  if "$badProblem" = true
  then
    echo "There was a bad problem. Exiting program now." >&2

    # ----------------------------------------------
    # BUG IS DEMONSTRATED HERE
    # ----------------------------------------------
    # The goal is to exit the script at this point,
    # but in this spot it will not work as expected
    # in every case, it will not always exit the 
    # script. TO DEMONSTRATE THE PROBLEM, UNCOMMENT
    # THIS LINE OF THE SCRIPT AND RUN IT.
    #   exit 1
    # Instead, to fix the problem, invoke the special
    # trap (created above) to exit the script at the
    # main level instead of from within this lower
    # level function. Here is the work-around:
    kill -s TERM $TOP_PID
    # That line combined with the trap above, works
    # around the problem.
  else
    echo "No bad problem found." >&2
  fi

  # New test: Additional test to see if the workaround
  # succeeds in a sub-sub routine.
  returnFromSubTest=$( SubTestForProblem )

  if "$subProblem" = true
  then
    echo "----------------------------------------------------------------" >&2
    echo "BUG: This line should not be reached, program should have exited." >&2
    echo "Returned data from Sub Test is $returnFromSubTest." >&2
    echo "----------------------------------------------------------------" >&2  
  fi

  echo "Returning data from function." >&2
  returnData="Some_Data"
  echo $returnData
}


# ----------------------------------------------
# Function: SUBTEST for a problem and kill the
# program if there is a problem. This will be
# called as a sub-sub-routine from the 
# TestForBadProblem function.
# ----------------------------------------------
SubTestForProblem()
{
  echo "SubTesting Now (subroutine from subroutine)..."  >&2
  if "$subProblem" = true
  then
    # If the work-around is working as I expect it to work,
    # the program should be killed here just as effectively
    # as if I had killed it from the parent function. But
    # it's not quite doing that. It seems to complete the
    # parent function before killing the program.
    echo "There was a subtest problem. Exiting program." >&2
    kill -s TERM $TOP_PID
  else
    echo "No sub problem found. Returning data" >&2
  fi
  
  subReturnData="Some_Sub_Data"
  echo $subReturnData
}


# ----------------------------------------------
# Main program code body
# ----------------------------------------------
echo "Starting program now."  >&2

# ----------------------------------------------
# First test - There should be no problem and it
# should return data from the function.
# ----------------------------------------------
echo "" >&2
echo "First test - No problem encountered, data retrieved." >&2
badProblem=false
subProblem=false
returnedData=$( TestForBadProblem )
echo "returnedData was: $returnedData" >&2

# ----------------------------------------------
# Test 1.5 - Try a sub-function
# ----------------------------------------------
echo "" >&2
echo "1.5 test - Only subProblem encountered, data retrieved." >&2
badProblem=false
subProblem=true
returnedData=$( TestForBadProblem )
echo "returnedData was: $returnedData" >&2

# ----------------------------------------------
# Second test - There should be a bad problem
# encountered and it should quit the program
# without trying to return any data at all.
# It shouldn't even reach the line that tries
# to echo the returned data.
# ----------------------------------------------
echo "" >&2
echo "Second test - Problem encountered, attempt to retrieve data, method 1." >&2
badProblem=true
subProblem=false
returnedData=$( TestForBadProblem )
echo "returnedData was: $returnedData" >&2

# ----------------------------------------------
# You should not reach this line of code because
# the program should have exited in the second
# test. However the program continues to this
# point, despite the documentation for "exit"
# indicating that it should truly exit rather
# than just act like a "return" statement.
# ----------------------------------------------
echo "" >&2
echo "----------------------------------------------------------------" >&2
echo "BUG: If you can read this, the program did not exit as expected." >&2
echo "----------------------------------------------------------------" >&2

# ----------------------------------------------
# Third test. Try a different way of reading the
# function's return data. This does not work, it
# just sets a string value rather than calling
# the function. The function never gets called.
# ----------------------------------------------
echo "" >&2
echo "Third test - Attempt to retrieve data, method 2." >&2
badProblem=true
subProblem=false
returnedData=TestForBadProblem
echo "returnedData was: $returnedData" >&2

# ----------------------------------------------
# Last test - Call the function but without
# trying to read any return data from it. This
# works as expected, but I want to read the data
# so I can't use this method.
# ----------------------------------------------
echo "" >&2
echo "Last test - Problem encountered, do not attempt to retrieve data." >&2
badProblem=true
subProblem=false
TestForBadProblem
echo "Program will not reach this line, you will not see it." >&2



Output from the code above:

Code:
Starting program now.

First test - No problem encountered, data retrieved.
Testing for a potential bad problem...
No bad problem found.
SubTesting Now (subroutine from subroutine)...
No sub problem found. Returning data
Returning data from function.
returnedData was: Some_Data

1.5 test - Only subProblem encountered, data retrieved.
Testing for a potential bad problem...
No bad problem found.
SubTesting Now (subroutine from subroutine)...
There was a subtest problem. Exiting program.
----------------------------------------------------------------
BUG: This line should not be reached, program should have exited.
Returned data from Sub Test is Some_Sub_Data.
----------------------------------------------------------------
Returning data from function.
_________________________
Tony Fabris