Why does the sequence matter in the execution of these bash commands?

There seems to be some inconsistency that I am not able to understand regarding the bash shell.

If I execute:

ls;date;time

the results of the three queries are shown in sequence.

However, on interchanging date and time position, an error message pops up.

So if I execute:

ls;time;date

the error message says: bash: syntax error near unexpected token 'date'.

Can someone explain this?


Solution 1:

The time command in your pipeline is not the /usr/bin/time binary, but the bash time built-in. Compare man time with help time. The error you see is bash failing to parse time's argument. This must either be present or be a newline. It is a newline in your first example but absent in the second.

On the other hand, if you were to run

ls;date;'time'

or

ls;'time';date

where the quotes around 'time' revoke its status as a reserved word, then bash has no problems parsing the line. It now parses three commands in a list, which it will execute in sequence, and /usr/bin/time will report a usage error in either case.

Addendum

It was observed that though time ; date yields an error, time ; ; date does not. The likely explanation is that time ; is interpreted by bash as equivalent to time <newline>. The expression time ; ; date is then parsed as the list of time ; and date.

This is consistent with the observation that time ; and time ; ; are legal as well, the second being parsed as the singleton list containing time ; followed by the optional semicolon allowed after lists.

So another way of explaining why time ; date yields the error bash: syntax error near unexpected token 'date' is that time consumes the semicolon separating it from date. It can only do that because time is a bash reserved word.

Solution 2:

Bash treats the built-in time as a special case, when parsing command-lines.

As can be read in the bash manpage, the line as typed is first split into a list:

pipeline ; pipeline

where a pipeline is:

[time [-p]] [ ! ] command [ [|⎪|&] command2 ... ]

or in our case, simply:

time command

i.e. if time is present, then command must also be present.

[There is a special case that allows time to be followed by a newline, but that doesn't apply here]

So, in our case, we have:

time;date

being split into two pipelines:

1. time
2. date

and pipeline 1 is not well formed, since we have time without a command. Hence the error.

Note that the command-line time doesn't work here either:

$ /usr/bin/time;date
Usage: /usr/bin/time [-apvV] [-f format] [-o file] [--append] [--verbose]

bash parses this as expected, into 2 pipelines:

1. /usr/bin/time
2. date

and /usr/bin/time then refuses to run with no argument. Note that this is an error from /usr/bin/time not an error from bash.

The reason that back-tick works is that the back-tick stops time being interpreted as a special element within the pipeline.

i.e. with the back-tick:

`time`;date

it is parsed as two pipelines:

1. `time`
2. date

Remember that a pipeline, in our case, is:

[time] command

and the problem initially was that we had time with no command, which isn't allowed. But now we simply have the command:

`time`

without the preceding time, since the back-ticks mean that time is interpreted as the command, not as a preceding word.

So bash then runs its builtin time with no args, which is accepted. It produces no output, and we see no error.

Note that:

`time`

actually runs the result of the time built-in, i.e. it runs whatever the time built-in produces on stdout. But since time on its own doesn't write anything to stdout, it appears to work.

Finally, it's been noted that this works:

time ; ; date

which I can't explain, sadly :)