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 ordate
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