83

I want to run time command to measure time of several commands.

What I want to do is:

  • Use the time command to measure the time it takes to run multiple commands together
  • Write only the time output to a file
  • Write the stderr of all commands I am measuring to stderr, not to the file

What I do NOT want to do is:

  • Write the several commands into a separate script
    • Why? because all of this is already a script that I am generating programatically, and creating ANOTHER temporary script would be more mess than I want.

What I have tried so far:

  1. /usr/bin/time --output=outtime -p echo "a"; echo "b";
    • Doesn't work, time is run only on the first one.
  2. /usr/bin/time --output=outtime -p ( echo "a"; echo "b"; )
    • Doesn't work, ( is unexpected token.
  3. /usr/bin/time --output=outtime -p { echo "a"; echo "b"; }
    • Doesn't work, "no such file or directory".
  4. /usr/bin/time --output=outtime -p ' echo "a"; echo "b";'
    • Doesn't work, "no such file or directory".
  5. time ( echo "a"; echo "b"; ) 2>outtime
    • Doesn't work, since it redirects all STDERR into outtime; I want only the time output there.
  6. And of course, time --output=outime echo "a";
    • Doesn't work, since --output=outime: command not found.

How can I do it?

1

4 Answers 4

108

Use sh -c 'commands' as the command, e.g.:

/usr/bin/time --output=outtime -p sh -c 'echo "a"; echo "b"'
5
  • 4
    a shorter version: time -p sh -c 'echo "a"; echo "b"'
    – Geo
    Jun 2, 2019 at 2:08
  • Is it expected that aliases won't work in the sequence of commands?
    – Muffo
    Aug 10, 2020 at 20:40
  • Although this answer works great, I prefer a version shared at superuser.com/a/608596/135762 because it makes the code much readable, instead of just being passed as a string to sh -c. Anyway, Can somebody please point a situation when one would be preferred over another? Aug 13, 2020 at 1:20
  • The version shared there runs multiple commands, but does not save the time output to a file, as this question requested. If you read this question closely, you'll see that using parenthesis to make a subshell was mentioned as not a solution.
    – Jim Paris
    Aug 14, 2020 at 19:32
  • 1
    @muffo You are spawning a new shell, so aliases will only work if the new shell you're spawning defines them too. You might find something like bash -i -c 'echo foo' to work better than sh -c 'echo foo' in that regard.
    – Jim Paris
    Aug 14, 2020 at 19:34
24

Not the correct answer but very related to the question.
Get timing statistics for multiple programs combined parentheses are required. Separate commands with semicolons ; or &&, if command2 should only be run, when command1 exited without error:

time ( command1 ; command2 )

time ( command1 && command2 )
3
  • 3
    This is nice. Better yet, use && between the commands -- like so time ( command1 && command2 ) so that if the first command fails; it won't proceed to executing the other. May 8, 2019 at 11:03
  • 2
    @bikashg It very much depends on what you want to run. In my case I use | because I'm piping between three commands. Apr 13, 2020 at 18:53
  • Very helpful. Upvoted. Building upon your answer, here's mine.. I show a multi-line command. Apr 20 at 15:05
9

Try this:

% (time ( { echas z; echo 2 } 2>&3 ) ) 3>&2 2>timeoutput
zsh: command not found: echas
2
% cat timeoutput                                
( { echas z; echo 2; } 2>&3; )  0.00s user 0.00s system 0% cpu 0.004 total

Explanation:

First, we have to find a way to redirect the output of time. Since time is a shell builtin, it takes the full command line as the command to be measured, including redirections. Thus,

% time whatever 2>timeoutput
whatever 2> timeoutput  0.00s user 0.00s system 0% cpu 0.018 total
% cat timeoutput 
zsh: command not found: whatever

[Note: janos's comment implies this is not the case for bash.] We can achieve the redirection of time's output by running time in a subshell and then redirecting the output of that subshell.

% (time whatever) 2> timeoutput
% cat timeoutput 
zsh: command not found: whatever
whatever  0.00s user 0.00s system 0% cpu 0.018 total

Now we have successfully redirected the output of time, but its output is mixed with the error output of the command we are measuring. To separate the two, we use an additional file descriptor.

On the "outside" we have

% (time ... ) 3>&2 2>timeout

This means: whatever is written to file descriptor 3, will be output to the same place file descriptor 2 (standard error) is outputting now (the terminal). And then we redirect standard error to the file timeout.

So now we have: everything written to stdout and fd 3 will go to the terminal, and everything written to stderr will go to the file. What's left is to redirect the measured command's stderr to fd 3.

% (time whatever 2>&3) 3>&2 2>timeout

Now, to make time measure more than one command, we need to run them in an(other!) subshell (inside parentheses). And to redirect the error output of all of them to fd 3, we need to group them inside curly brackets.

So, finally, we arrive at:

% (time ( { whatever; ls } 2>&3 ) ) 3>&2 2>timeoutput

That's it.

2
  • This is a syntax error in a POSIX shell. Probably a bashism?
    – josch
    Aug 1, 2017 at 6:00
  • @josch the shell used here is zsh.
    – angus
    Aug 28, 2017 at 13:22
0

This is not an answer to the OP's question. The OP would like to store only the output of the time part of the command into the file, not the stderr output of all of the subcommands also into the file. My approach below, however, will optionally store all output into the file, including the stderr of all subcommands. That's what I want, not what the OP wants.

My answer is therefore only an answer to the title of the question, which is:

How to run time on multiple commands AND write the time output to file?

How to time a multiline, multi-subcommand command in bash, and optionally store all output into a file

ie: How to time multiple sub-commands and the totality of the whole group of commands using parenthesis (( )) in bash, and optionally store the entire output into a file.

To build a bit more on @Martin.T's answer, here is the format with parenthesis that I like to use when I have a long and complicated multi-line command. It kind of reminds me of Python a bit, because it is prettier to look at than most bash I might see.

Running time on the individual sub-commands is optional. Use ; or && as you see fit after each subcommand.

  • ; is used after a command to indicate "run the next command no matter what (ex: even if this one fails)", and
  • && is used after a command to indicate: "only run the next command if this one passes".
  • The backslashes (\) after each line indicate that the command is a multi-line command and continues on to the next line. For a multi-line command, you must have a backslash after each line except the last one.

Example general format:

# Option 1: only run the command
time ( \
   time command1; \
   time command2 && \
   time command3 && \
   time command4 \
)

To also store the entire output into a file named output.txt, do this instead. Description:

  1. The extra set of outer parenthesis captures the output from the first, outer-most bash built-in time command as well,
  2. the 2>&1 part redirect stderr (file descriptor 2) to stdin (file descriptor 1), since time outputs its results to stderr otherwise, and
  3. piping to the tee command with | tee output.txt causes the results, which are now on stdout, to both be displayed to the screen and to be written to the output.txt file.
# Option 2: run the command _and_ store the results into an `output.txt` file
(time ( \
   time command1; \
   time command2 && \
   time command3 && \
   time command4 \
)) 2>&1 | tee output.txt

Example demonstration commands:

# Option 1: only run the command
time ( \
   time sleep 1; \
   time sleep 2 && \
   time sleep 3 && \
   time sleep 1 \
)

# Option 2: run the command _and_ store the results into an `output.txt` file
(time ( \
   time sleep 1; \
   time sleep 2 && \
   time sleep 3 && \
   time sleep 1 \
)) 2>&1 | tee output.txt

Example demo command and output:

Notice that you can see the time of each subcommand as 1 sec, 2 sec, 3 sec, 1 sec, followed by the total time of 7 sec:

$ time ( \
>    time sleep 1; \
>    time sleep 2 && \
>    time sleep 3 && \
>    time sleep 1 \
> )

real  0m1.013s
user  0m0.002s
sys   0m0.000s

real  0m2.001s
user  0m0.001s
sys   0m0.000s

real  0m3.001s
user  0m0.001s
sys   0m0.000s

real  0m1.001s
user  0m0.000s
sys   0m0.001s

real  0m7.017s
user  0m0.005s
sys   0m0.001s

If you run the "Option 2" command above, you'll also see this exact same output above now stored into the just-created output.txt file. View the contents of that file with:

cat output.txt

Follow-up question

I'm trying to figure out a little bit more about storing the output into a file in a more granular way, like the OP wants, but using the technique I've posted above. I've posted this follow-up question of mine here: How to store inner output of a nested bash command (within parenthesis) into one file and outer output into another file?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.