Search code examples
bashcommand-substitution

Streaming command substitution output to terminal w/o squelching color


Is there a way to show the output of a command substitution, in color, while it is executing, while still storing it to a variable? This would mainly be for testing/debugging purposes, and the output would not be shown normally.

If I do this:

output=$(some_command)
if [ "$debug" ]; then
  echo "$output"
fi

...then this is close, but not what I want in a few ways:

  • Even if the command would have otherwise emitted output to the terminal in color, it's printed in black-and-white.
  • The output is only shown after the command is finished, rather than streaming as it runs.

How can I conditionally stream output to a terminal without squelching color?


Solution

  • To store a command's results to a variable as well as streaming its output to the console:

    var=$(some_command | tee /dev/stderr)
    

    If you want to force your command to think it's outputting directly to a TTY, and thus to enable color output if it would do so when not in a pipeline, use the tool unbuffer, shipped with expect:

    var=$(unbuffer some_command | tee /dev/stderr)
    

    All that said: If you only want to show debugging conditionally for a long script, it makes sense to put that conditional up front at the top of your script rather than scattering it around everywhere. For instance:

    # put this once at the top of your script
    set -o pipefail
    if [[ $debug ]]; then
      exec 3>/dev/stderr
    else
      exec 3>/dev/null
    fi
    
    # define a function that prepends unbuffer only if debugging is enabled
    maybe_unbuffer() {
      if [[ $debug ]]; then
        unbuffer "$@"
      else
        "$@"
      fi
    }
    
    # if debugging is enabled, pipe through tee; otherwise, use cat
    capture() {
      if [[ $debug ]]; then
        tee >(cat >&3)
      else
        cat
      fi
    }
    
    # ...and thereafter, when you want to hide a command's output unless debug is enabled:
    some_command >&3
    
    # ...or, to capture its output while still logging to stderr without squelching color...
    var=$(maybe_unbuffer some_command | capture)