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
  • 133,243
  • 15
  • 236
  • 397
MathematicalOrchid
  • 5,664
  • 7
  • 35
  • 62
  • @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?](https://unix.stackexchange.com/q/444509) – 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](https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/time.html) (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
  • 320,670
  • 36
  • 633
  • 936
1

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

echo foo | /usr/bin/time sleep 1
  • 1
    Actually, this is what Bash was trying to do in the first place; the problem was `/usr/bin/time` doesn't exist on this particular system. – MathematicalOrchid Jun 16 '21 at 08:25