How to Capture and Store time Command Output in Bash Variables


2 views

When working with the time command in bash, you'll notice it doesn't behave like regular commands when you try to capture its output:

# This doesn't work as expected
X=$(time ls)
echo "$X"  # Only shows ls output, not timing info

The timing information gets printed to stderr instead of stdout, which is why standard output redirection fails.

There are actually three versions of time in Unix-like systems:

  • The bash built-in (what you get when you type just time)
  • The /usr/bin/time command (GNU time)
  • The POSIX time command

The bash built-in writes to stderr by design, while GNU time has more flexible output control.

For bash built-in time, you can redirect stderr to a file:

TIMEFILE=$(mktemp)
(time ls) 2> "$TIMEFILE"
X=$(cat "$TIMEFILE")
rm "$TIMEFILE"
echo "Timing info: $X"

This approach works with bash's time:

{ time ls; } 2>&1 | grep real

If you have GNU time installed (/usr/bin/time), it offers better formatting options:

X=$(/usr/bin/time -p ls 2>&1)
echo "$X" | grep real

Or with custom formatting:

X=$(/usr/bin/time -f "%E real,%U user,%S sys" ls 2>&1)
echo "$X"

For complex timing operations:

exec 3>&1 4>&2
X=$( { time ls 1>&3 2>&4; } 2>&1 )
exec 3>&- 4>&-
echo "Timing: $X"

Here's how you might time a script and store results:

#!/bin/bash

TIMEFILE=$(mktemp)

# Time the operation
{ time ./long_script.sh; } 2> "$TIMEFILE"

# Extract timing info
real_time=$(grep real "$TIMEFILE")
user_time=$(grep user "$TIMEFILE")
sys_time=$(grep sys "$TIMEFILE")

# Store in variables
echo "Real time: $real_time"
echo "User time: $user_time"
echo "System time: $sys_time"

rm "$TIMEFILE"

When benchmarking, consider:

  • The time command itself adds overhead
  • For microbenchmarks, consider using times builtin or date commands
  • Multiple runs can provide more accurate measurements
# Using date for simple timing
START=$(date +%s.%N)
# commands to time
END=$(date +%s.%N)
DIFF=$(echo "$END - $START" | bc)
echo "Time taken: $DIFF seconds"

When working with Bash scripting and performance measurement, many developers encounter this common frustration: the time command's output bypasses standard streams and goes straight to stderr. This makes capturing its output non-trivial compared to regular command output.

Try this simple test:

# This won't work as expected
X=$(time ls)
echo "$X"

You'll notice the timing statistics appear in the console rather than being stored in variable X. This happens because time writes to standard error (stderr) by default.

Solution 1: Redirect stderr to stdout

The most straightforward approach is to redirect stderr to stdout:

# Capture both stdout and stderr
X=$( { time ls; } 2>&1 )
echo "$X"

Solution 2: Using the full path to time

Bash has a built-in time command, but using the full path to the system's time binary behaves differently:

# Using system time command
X=$( /usr/bin/time -p ls 2>&1 )
echo "$X" | grep real

Solution 3: Process Substitution

For more complex processing, especially when you need specific timing metrics:

# Get just the real time
real_time=$( { time ls >/dev/null 2>&1; } 2>&1 | grep real | awk '{print $2}')
echo "Execution took $real_time seconds"

For benchmarking scripts, you might want a more robust solution:

# Function to measure and return time
timeit() {
    { time "$@" >/dev/null 2>&1; } 2>&1 | grep real | awk '{print $2}'
}

duration=$(timeit sleep 2)
echo "Operation took ${duration}s"

If you have GNU time installed, you can customize the output format:

# Custom formatted output
/usr/bin/time -f "%e" ls 2>&1 >/dev/null

The format specifiers include:

- %e: Elapsed real time

- %U: User CPU time

- %S: System CPU time

  • Always test time measurement in your specific environment
  • Consider using specialized benchmarking tools for production code
  • When comparing times, run multiple iterations
  • Be aware of shell built-in vs system time command differences