How to Use tee Command to Both Display Output and Assign Grep Results to a Variable in Bash


2 views

When working with command-line tools in Bash, we often encounter situations where we need to:

  • View the full command output in real-time
  • Simultaneously process that output (e.g., grep for specific patterns)
  • Store the processed result in a variable for later use

The OP's initial attempts didn't work because:

mycommand | tee myvar=$(grep -c keyword)  # Incorrect
mycommand | tee >(myvar=$(grep -c keyword))  # Subshell scope issue

Bash variable assignment in pipelines creates subshells where variable changes don't persist to the parent shell.

Method 1: Process Substitution with Temporary File

myvar=$(mycommand | tee >(grep -c keyword > /tmp/tmpfile) >/dev/null)
myvar=$(cat /tmp/tmpfile)
rm /tmp/tmpfile

Method 2: Named Pipe (FIFO)

mkfifo mypipe
mycommand | tee mypipe | grep -c keyword > myvar &
cat mypipe
wait
echo "Matches: $myvar"

Method 3: Bash Process Substitution with File Descriptor

exec 3>&1
myvar=$(mycommand | tee >(grep -c keyword >&3) | grep -c keyword)
exec 3>&-

Method 4: Using pee from moreutils

myvar=$(mycommand | pee 'grep -c keyword' 'cat')

For most use cases, the cleanest solution is:

# Store output in variable while displaying it
output=$(mycommand | tee /dev/tty)
myvar=$(grep -c keyword <<< "$output")

This approach:

  • Preserves all output to terminal
  • Captures full output in a variable
  • Allows multiple processing steps
  • No temporary files needed

Here's how you might monitor application logs while counting errors:

log_errors=$(tail -f /var/log/app.log | tee >(grep -c "ERROR" > error_count.txt) >/dev/null)
# In another terminal:
cat error_count.txt

For high-volume streams:

  • Method 2 (FIFO) has lowest overhead
  • Avoid multiple greps on large outputs
  • Consider buffering (e.g., stdbuf -oL) for interactive programs
stdbuf -oL mycommand | tee >(grep -c keyword > output) | cat

In zsh, you can use =(...) process substitution:

myvar=$(grep -c keyword =(mycommand | tee /dev/tty))

If your variable remains empty:

set -x  # Enable debugging
# Your command here
set +x  # Disable debugging

When working with Linux command-line pipelines, we often need to both see the full output on screen while simultaneously processing parts of that output through other commands like grep and storing results in variables. The tee command seems perfect for this, but the syntax can be tricky to get right.

The failed approaches in the question reveal common misunderstandings:


# Problem 1: Command substitution happens first
mycommand | tee myvar=$(grep -c keyword)  # grep runs before mycommand

# Problem 2: Process substitution scope
mycommand | tee >(myvar=$(grep -c keyword))  # Variable assignment happens in subshell

Solution 1: Using Process Substitution with Global Variable

This preserves the variable in the current shell:


myvar=$(mycommand | tee /dev/tty | grep -c keyword)

Solution 2: Multi-stage Processing with tee

For more complex processing:


{
  mycommand | tee >(grep keyword > matches.txt) \
              >(grep -v keyword > non_matches.txt) \
              | grep -c keyword > count.txt
  myvar=$(

Solution 3: Named Pipe Alternative

For persistent monitoring scenarios:


mkfifo mypipe
mycommand | tee mypipe &
myvar=$(grep -c keyword < mypipe)

When working with colorized output:


myvar=$(mycommand | tee >(cat >&2) | grep -c --color=always keyword)

For high-volume streams, these methods have different characteristics:

  • Process substitution adds minimal overhead
  • Named pipes have slightly more overhead but better for continuous streams
  • Variable assignment is fastest for small outputs

Always include error handling:


if ! myvar=$(mycommand 2>&1 | tee /dev/tty | grep -c keyword); then
  echo "Error occurred" >&2
  exit 1
fi