Differences between revisions 2 and 18 (spanning 16 versions)
Revision 2 as of 2007-06-14 20:51:30
Size: 1496
Editor: GreyCat
Comment: expand. and correct. my god, correct that wrong stuff!
Revision 18 as of 2025-01-23 23:48:56
Size: 2718
Editor: larryv
Comment: cleaned up after sloppy edit; added more empty lines to separate examples
Deletions are marked like this. Additions are marked like this.
Line 1: Line 1:
[[Anchor(faq32)]] <<Anchor(faq32)>>
Line 6: Line 6:
   time find ... | xargs ... time find ... | xargs ...
}}}
and get the execution time of the entire pipeline, rather than just the simple command at the start of the pipe. (This is different from the behavior of the external command {{{time(1)}}}, for obvious reasons.)

Because of this, people who want to redirect {{{time}}}'s output often encounter difficulty figuring out where all the file descriptors are going. It's not as hard as most people think, though -- the trick is to call {{{time}}} in a SubShell or block, and then capture stderr of the subshell or block (which will contain {{{time}}}'s results). If you need to redirect the actual command's stdout or stderr, you do that inside the subshell/block. For example:

 * File redirection:
 {{{
# Explicit, but inefficient.
bash -c "time ls" 2>time.output

# Slightly more efficient.
( time ls ) 2>time.output

# Most efficient.
{ time ls; } 2>time.output

# The general case:
{ time some command >stdout 2>stderr; } 2>time.output
Line 9: Line 27:
and get the execution time of the entire pipeline, rather than just the simple command at the start of the pipe. (This is different from the behavior of the external command {{{time(1)}}}, for obvious reasons.)  * CommandSubstitution:
 {{{
# Captures *everything*.
foo=$( bash -c "time ls" 2>&1 )
Line 11: Line 32:
Because of this, people who want to redirect {{{time}}}'s output often encounter difficulty figuring out where all the file descriptors are going. It's not as hard as most people think, though -- the trick is to call {{{time}}} in a different shell or block, and redirect stderr of ''that'' shell or block (which will contain {{{time}}}'s results). If you need to redirect the actual command's stdout or stderr, you do that inside the inner shell/block. For example: # More efficient version.
foo=$( { time ls; } 2>&1 )
Line 13: Line 35:
 * File Redirection
{{{
   bash -c "time ls" 2>time.output
   ( time ls ) 2>time.output
   { time ls; } 2>time.output
   # The general case:
   { time some command >stdout 2>stderr; } 2>time.output
# Captures stderr and time, but not stdout.
# The shell's original FD 1 is saved in FD 3, which is inherited by the subshell.
# Inside the innermost block, we send the time command's stdout to FD 3.
exec 3>&1
foo=$( { time bar 1>&3; } 2>&1 )
exec 3>&-

# Captures time, but not stdout and stderr.
exec 3>&1 4>&2
foo=$( { time bar 1>&3 2>&4; } 2>&1 )
exec 3>&- 4>&-

# Same thing without exec
{ foo=$( { time bar 1>&3- 2>&4-; } 2>&1 ); } 3>&1 4>&2
}}}
 See FileDescriptor for full explanations of the redirection juggling.

 * Pipe:
 {{{
# Make time only output elapsed time in seconds
TIMEFORMAT=%R
# Keep stdout and stderr unmolested
exec 3>&1 4>&2
{ time foo 1>&3 2>&4; } 2>&1 | awk '{
    printf "The task took %d hours, %d minutes and %.3f seconds\n",
           $1/3600, $1%3600/60, $1%60
}'
exec 3>&- 4>&-
Line 22: Line 65:
 * Variable Redirection A similar construct can be used to capture "core dump" messages, which are actually printed by the shell that launched a program, not by the program that just dumped core:
Line 24: Line 68:
   foo=$( bash -c "time ls" 2>&1 ) # Captures *everything*. # Fails to capture the message
./coredump >log 2>&1
Line 26: Line 71:
   # Keep stdout unmolested.
   exec 3>&1
   foo=$( { time bar 1>&3; } 2>&1 ) # Captures stderr and time.
   exec 3>&-
# Captures the message
{ ./coredump; } >log 2>&1
}}}
Line 31: Line 75:
   # Keep both stdout and stderr unmolested.
   exec 3>&1 4>&2
   foo=$( { time bar 2>&4; } 2>&1 1>&3) # Captures time only.
   exec 3>&- 4>&-
The same applies to job control messages:

{{{
$ { sleep 1 & } >log 2>&1
$ cat log
[1] 10316
[1]+ Done sleep 1
Line 36: Line 83:

Of course you may opt to redirect to `/dev/null` instead of a file.

----
CategoryShell CategoryExampleCode

How can I redirect the output of 'time' to a variable or file?

Bash's time keyword uses special trickery, so that you can do things like

time find ... | xargs ...

and get the execution time of the entire pipeline, rather than just the simple command at the start of the pipe. (This is different from the behavior of the external command time(1), for obvious reasons.)

Because of this, people who want to redirect time's output often encounter difficulty figuring out where all the file descriptors are going. It's not as hard as most people think, though -- the trick is to call time in a SubShell or block, and then capture stderr of the subshell or block (which will contain time's results). If you need to redirect the actual command's stdout or stderr, you do that inside the subshell/block. For example:

  • File redirection:
    # Explicit, but inefficient.
    bash -c "time ls" 2>time.output
    
    # Slightly more efficient.
    ( time ls ) 2>time.output
    
    # Most efficient.
    { time ls; } 2>time.output
    
    # The general case:
    { time some command >stdout 2>stderr; } 2>time.output
  • CommandSubstitution:

    # Captures *everything*.
    foo=$( bash -c "time ls" 2>&1 )
    
    # More efficient version.
    foo=$( { time ls; } 2>&1 )
    
    # Captures stderr and time, but not stdout.
    # The shell's original FD 1 is saved in FD 3, which is inherited by the subshell.
    # Inside the innermost block, we send the time command's stdout to FD 3.
    exec 3>&1
    foo=$( { time bar 1>&3; } 2>&1 )
    exec 3>&-
    
    # Captures time, but not stdout and stderr.
    exec 3>&1 4>&2
    foo=$( { time bar 1>&3 2>&4; } 2>&1 )
    exec 3>&- 4>&-
    
    # Same thing without exec
    { foo=$( { time bar 1>&3- 2>&4-; } 2>&1 ); } 3>&1 4>&2

    See FileDescriptor for full explanations of the redirection juggling.

  • Pipe:
    # Make time only output elapsed time in seconds
    TIMEFORMAT=%R
    # Keep stdout and stderr unmolested
    exec 3>&1 4>&2
    { time foo 1>&3 2>&4; } 2>&1 | awk '{
        printf "The task took %d hours, %d minutes and %.3f seconds\n",
               $1/3600, $1%3600/60, $1%60
    }'
    exec 3>&- 4>&-

A similar construct can be used to capture "core dump" messages, which are actually printed by the shell that launched a program, not by the program that just dumped core:

# Fails to capture the message
./coredump >log 2>&1

# Captures the message
{ ./coredump; } >log 2>&1

The same applies to job control messages:

$ { sleep 1 & } >log 2>&1
$ cat log
[1] 10316
[1]+  Done                    sleep 1

Of course you may opt to redirect to /dev/null instead of a file.


CategoryShell CategoryExampleCode

BashFAQ/032 (last edited 2025-01-23 23:48:56 by larryv)