11

Consider the following:

# time sleep 1

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

echo foo | time sleep 1

bash: time: command not found

Um... wut?

OK, so clearly Bash is searching for commands in a somehow different way when run as a pipeline. Can anyone explain to me what the difference is? Does piping disable shell built-ins or something? (I didn't think it did... but... I can't see how else this is breaking.)

ilkkachu
  • 138,973
  • @Kusalananda time is usually a shell built-in and an external program. Although on this system, the external command isn't installed for some reason. Also: I specifically want to time one part of the pipeline, not the entire thing. – MathematicalOrchid Jun 14 '21 at 10:16
  • 9
    time is not a shell built-in, but a keyword. See also How can we make `time` apply to a pipeline or its component? – Kusalananda Jun 14 '21 at 10:31
  • 1
    @Kusalananda point is that time command runs the shell keyword, but echo foo | time command runs the binary. I can reproduce this on my Arch Linux system. And that is indeed surprising. – terdon Jun 14 '21 at 11:27
  • 3
    @terdon It's not surprising. The keyword time is only allowed in certain places in the grammar. In particular, it's part of the syntax for a pipeline. It's specifically allowed at the start of a pipeline: [time [-p]] [ ! ] command [ | command2 … ]. Using time at any other place would call the external utility. If there isn't one, then you get the obvious error message from the shell. – Kusalananda Jun 14 '21 at 11:45
  • 3
    Please note that "time one part of the pipeline" might not be doing what you want – all the commands of a pipeline might be running in parallel. – Paŭlo Ebermann Jun 14 '21 at 19:43
  • 3
    and POSIX leaves time in pipelines unspecified (stuff like time a | b | c and a | b | time c), likely exactly because some shells have it as a keyword that times the full pipeline, and some don't. "When time is used as part of a pipeline, the times reported are unspecified, except when it is the sole command within a grouping command" – ilkkachu Jun 14 '21 at 22:34

2 Answers2

20

The bash shell implements time as a keyword. The keyword is part of syntax of the pipeline.

The syntax of a pipeline in bash is (from the section entitled "Pipelines" in the bash manual):

[time [-p]] [!] command1 [ | or |& command2 ] …

Since time is part of the syntax of pipelines, not a shell built-in utility, it does not behave as a utility. For example, redirecting its output using ordinary shell redirections is not possible without extra trickery (see e.g. How can I redirect `time` output and command output to the same pipe?).

When the word time occurs in any other place than at the start of a pipeline in the bash shell, the external command with the same name will be called. This is what happens in the case when you put time after the pipe symbol, for example. If the shell can't find an external time command, it generates a "command not found" error.

To make the shell use the keyword to time only the sleep 1 command in your pipeline, you may use

echo foo | (time sleep 1)

Within the subshell on the right hand side of the pipeline, the time keyword is at the start of a pipeline (a pipeline of a single simple command, but still).

Also related:

Kusalananda
  • 333,661
1

You may also use /usr/bin/time which can give you more details too:

echo foo | /usr/bin/time sleep 1