5

Usually I capture the output via a subshell: result="$(command 2>&1)"

If the command is source, the subshell swallows some (all?) changes to the scripts shell's environment.

How can I capture the output of source into a variable?

pico_prob
  • 1,105
  • 10
  • 14
  • What is the point of capturing the output of `source`? – oguz ismail Mar 27 '21 at 14:12
  • Yes, all changes are lost when the subshell exits (unless there are files written). A subshell is not allowed to alter the parent's environment. – glenn jackman Mar 27 '21 at 15:03
  • @oguz ismail: error-handling – pico_prob Mar 27 '21 at 15:26
  • That is usually done using exit statuses of commands. – oguz ismail Mar 27 '21 at 15:28
  • @oguz ismail: That is what I do. I also like to log :) – pico_prob Mar 27 '21 at 15:29
  • @Philippe: If I get this right, you'll run into the subshell isolation problem (env vars are lost) – pico_prob Mar 27 '21 at 15:34
  • If you own the script you're sourcing, you could (instead of sourcing it) make it produce a set of assignments like `VAR=value` on standard output (keep standard error for errors and logging only). Then invoke it with `eval "$(bash myscript.sh)"`. This is what pyenv does. – Thomas Mar 27 '21 at 19:12
  • @oguzismail My reason is because my bashrc sources a script that produces lots of output. I would redirect to `/dev/null` but then if there is an error I don't see what went wrong. I want to capture the output and print it only if sourcing the file returned a non-zero code. – Philippe Carphin Jul 04 '22 at 15:43

1 Answers1

10

Surprisingly tricky question!

My first thought was to use a named pipe (mkfifo(1)), but those have a finite buffer size, so if the sourced script fills up the buffer the script would hang. And you can't use a background process to drain the buffer because you want the output in a variable in the original process eventually.

I'm sure there's a way to make it work entirely in memory, but in the end I think a simple and stupid redirect to a temporary file is the most straightforward and robust solution:

OUTPUT_FILE=$(mktemp)
source other_script.sh >$OUTPUT_FILE 2>&1
OUTPUT="$(< "$OUTPUT_FILE")"
rm -f "$OUTPUT_FILE"

(See this question and in particular BashFAQ 062 for security implications of mktemp though.)

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • 1
    I think this is an nifty, clean solution. I don't mind leaving the RAM for a moment :) I liked it from the beginning, only marked now as 'accepted' to wait for other answers. – pico_prob Apr 22 '21 at 09:14