💾 Archived View for thrig.me › blog › 2023 › 02 › 04 › a-tale-of-two-times.gmi captured on 2023-04-26 at 13:22:17. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-04-19)
-=-=-=-=-=-=-
< ngelover> time -l says unknown option to me, shouldn't it print the usage() < thrig> which time are you running? < ngelover> time(1) < thrig> really? < ngelover> well yes
The claim is that "time" disagrees with the documentation for time(1) on OpenBSD, and the evidence for this may look pretty solid:
$ man 1 time | fgrep -- -l time [-lp] utility [argument ...] -l The contents of the rusage structure are printed. The flag [-l] is an extension to that specification. $ time -l sleep 1 ksh: time: -l unknown option
Documentation problems are not unknown, for example cal(1) has a slightly complicated interface in that a single argument is taken as a year, and that slot instead becomes the month if a second number is provided...but the error message only mentions the month, not the maybe instead a year thing:
$ cal Germinal cal: invalid month: use 1-12 or a name
However, in this case both the time(1) manual and "time" are correct. The problem here, as alluded to by my question--"which time are you running?"--is that there are multiple implementations of "time". The ksh shell has a reserved word "time" that will be run in favor of anything found in PATH:
time [-p] [pipeline] The time reserved word is described in the Command execution section.
Note the lack of an -l flag in this documentation.
Other bournelikes--shells vaguely based on /bin/sh--are similar; ZSH also has an internal "time" or can run whatever turns up in PATH:
$ exec zsh % whence -a time time /usr/bin/time % exec ksh
There are various ways to bypass the internal "time" command, if you really do want to use time(1) and not the one provided by the shell,
/usr/bin/time -l sleep 1 command time -l sleep 1 \time -l sleep 1 exec time -l sleep 1
though that last one might not be useful if the terminal output vanishes, and the \time trick to bypass aliases is also a bit clever and probably should not be used.
$ alias echo='builtin echo ohce' $ echo foo ohce foo $ \echo foo foo $ unalias echo
Note that "echo" also has shell-internal and external command implementations, and like "time" the different implementations vary. Hence the recommendation to use printf(1) in shell scripts that make at least some claim to portability.
$ which bash which: bash: Command not found.
Anyways, how can you know what "time" command you are actually running, besides from studying the system for probably too long? One way is process tracing; our hypothesis is that we are running time(1), so ideally we would like to see evidence for that. Care must be taken to setup a correct test: are you running the right commands, and tracing the right processes?
$ ktrace -i ksh -c 'time sleep 1' 0m01.00s real 0m00.00s user 0m00.00s system $ kdump | grep fork 34187 ksh CALL fork() 34187 ksh RET fork 70455/0x11337 70455 ksh RET fork 0
The fork here is ksh launching sleep(1); there is no time(1) being run. If time(1) were being run, it should appear as " 1234 time" for some random process ID, which it does if we use the fully qualified path to time:
$ kdump | perl -nle 'print if m/^\s*\d+\s+time/a' $ ktrace -i ksh -c '/usr/bin/time sleep 1' 1.00 real 0.00 user 0.00 sys $ kdump | perl -nle 'print if m/^\s*\d+\s+time/a' | sed 1q 67399 time NAMI "/usr/libexec/ld.so" $ kdump | grep fork 74945 ksh CALL fork() 74945 ksh RET fork 67399/0x10747 67399 ksh RET fork 0 67399 time CALL vfork() 67789 time RET vfork 0 67399 time RET vfork 67789/0x108cd
A major problem here is that we must have a doubt that time(1) is actually being run; knowledge of shell reserved words and builtins would help increase that doubt. Lacking doubt, it is easier to claim a documentation problem or some other bug on the assumption that time(1) is being run.
tags #debug #sh #unix #zsh