If I am understanding your question correctly, I think this may do it—
#!/bin/bash
done=
functionA() {
sleep 3 # Placeholder for the actual work functionA does
done=y # Tell the outer loop we're done
kill "$1" # Stop the progress indicator
}
functionB() {
while :; do # The progress indicator - run forever
echo .
sleep 1
done
}
until [[ "$done" ]]; do
functionB &
functionA $!
done
$! is the PID of the last-spawned background process, in this case a bash subshell (a separate process) running functionB in the background.
I think functionB was always seeing $a as 0 because functionA was run in a subshell, so its changes to a never made it back to the main shell where functionB was running.
By contrast, the code above runs functionB in the subshell and lets functionA expressly kill that subshell from the main shell. That way functionA's changes to done are visible in the until loop (I think).
Example output:
$ ./test.sh
.
.
.
$
Another option
If functionA may have to run more than once, you can use this:
#!/bin/bash
done=
functionA() {
sleep 3 # Do work
done=y # If functionA needs to run again, don't set $done
}
functionB() {
while :; do # Run forever, until terminated
echo .
sleep 1
done
}
functionB & # Start the progress indicator
progress_pid="$!"
until [[ "$done" ]]; do # Do the work
functionA
done
kill "$progress_pid" # Stop the progress indicator