piping main method to tee will make trap method not see global variables

Viewed 44

I have a trap method which neither access global variables nor receive variables using $*. It looks like this:

#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'

declare -a arr

finish_exit() {
        echo "* = $*"
        echo "arr = ${arr[*]}"
}
trap 'finish_exit "${arr[@]}"' EXIT

main() {
  arr+=("hello")
  arr+=("world")
}

main | tee -a /dev/null

The script prints ''.

If I remove the | tee -a ... snippet, the script prints 'hello\nworld' twice as expected.

Now, how can I pipe the output to a logfile WITHOUT loosing all context? One solution would be to route everything from the script call, like so: ./myscript.sh >> /dev/null, but I think there should be a way to do this inside the script so I can log EVERY call, not just those run by cron.

Another solution I investigated was:

main() {
  ...
} >> /dev/null

But this will result in no output on the interactive shell.


Bonus karma for explanations why this subshell will "erase" global variables before the trap function is being called.

1 Answers

will make trap method not see global variables

The subshell does "sees" global variables, it does not execute the trap.

why this subshell will "erase" global variables

From https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html :

Command substitution, commands grouped with parentheses, and asynchronous commands are invoked in a subshell environment that is a duplicate of the shell environment, except that traps caught by the shell are reset to the values that the shell inherited from its parent at invocation.

how can I pipe the output to a logfile WITHOUT loosing all context?

#!/bin/bash
exec 1> >(tee -a logfile)
trap 'echo world' EXIT
echo hello

or

{
   trap 'echo world' EXIT
   echo hello
} | tee -a logfile

And research: https://serverfault.com/questions/103501/how-can-i-fully-log-all-bash-scripts-actions and similar.


The following script:

#!/usr/bin/env bash
set -euo pipefail
{
set -euo pipefail
IFS=$'\n\t'

declare -a arr

finish_exit() {
        echo "* = $*"
        echo "arr = ${arr[*]}"
}
trap 'finish_exit "${arr[@]}"' EXIT

main() {
  arr+=("hello")
  arr+=("world")
}

main 

} | tee -a /dev/null

outputs for me:

* = hello
world
arr = hello
world

I added that set -o pipefail before the pipe, to preserve the exit status.

Related