Update metadata and transcripts through end of July 2026
Refreshed episodes/hosts/comments/series from hpr.sql, and added official HPR transcripts for the 180 episodes aired since the last sync (hpr4516-hpr4695).
This commit is contained in:
@@ -0,0 +1,441 @@
|
||||
Episode: 4678
|
||||
Title: High Resolution Elapsed Time in Shell Scripts
|
||||
Source: https://hub.hackerpublicradio.org/ccdn.php?filename=/eps/hpr4678/hpr4678.mp3
|
||||
Transcribed: 2026-07-31 16:16:39 (official HPR transcript)
|
||||
|
||||
---
|
||||
|
||||
This is Hacker Public Radio Episode 4678, for 2026-07-08
|
||||
Today's show is entitled, "High Resolution Elapsed Time in Shell Scripts"
|
||||
The host is Whiskeyjack and the duration is 00:28:49
|
||||
The flag is Clean, and the license is CC-BY-SA
|
||||
The summary is "Surprises encountered when measuring elapsed time in shell scripts"
|
||||
In this episode, I will describe how to calculate elapsed time in bash or other shell scripts.
|
||||
Well, this may sound like a very simple and basic thing to do.
|
||||
There's a slightly more complex aspect to it if you wish to calculate elapsed time
|
||||
to a higher resolution than one second.
|
||||
There are many reasons for calculating elapsed time in a shell script.
|
||||
For example, you may wish to simply report how long an operation took to run.
|
||||
Another reason may be that you're trying to speed up a script
|
||||
and need to calculate benchmark data to see how different alternatives methods perform.
|
||||
What may seem like a simple task gets a bit more complicated
|
||||
if you want to do it for multiple different operating systems,
|
||||
even if they are all unix-related as we shall see.
|
||||
Operating systems tested.
|
||||
For the purposes of this episode, I ran tests on the current version of the following operating systems.
|
||||
Elma, Alpine, Debian, FreeBSD, OpenBSD, Resbury Pi, OpenSouth,
|
||||
Ubuntu 2604.
|
||||
Elma is a close copy of Red Hat that we can take as representing Red Hat style distros.
|
||||
Simple, low-resolution timing.
|
||||
I will start with the simple and obvious method before describing the less obvious ones.
|
||||
This uses the date command to get the current time in seconds since the Unix epoch.
|
||||
This is simply date that's D-A-T-E, space, single-quote, plus percent, lower-case S, single-quote.
|
||||
Save this to a variable using whatever method you prefer.
|
||||
For example, start time equals dollar sign, left bracket, date, space, single-quote, plus percent S, single-quote, right bracket.
|
||||
Next, do whatever operations it is you wish to time.
|
||||
Use the date command to get the current time again.
|
||||
End time equals dollar sign, left bracket, date, space,
|
||||
Quote, plus percent S, quote, right bracket.
|
||||
Now, simply subtract the start time from the end time using shell arithmetic.
|
||||
This should be very obvious and basic.
|
||||
Higher resolution timing.
|
||||
However, suppose we wish to measure time to greater than one second of precision.
|
||||
We need to do two things.
|
||||
The first is to obtain the current time at a higher degree of precision.
|
||||
The second is to conduct the calculations to a higher degree of precision.
|
||||
Unfortunately, the standard time precision for posit shells seems to be one second.
|
||||
Some shells offer a higher precision, but others do not.
|
||||
Furthermore, standard shell arithmetic uses integer, which limits calculations to one second of precision.
|
||||
Bash, high-resolution shell variable.
|
||||
Fortunately, Bash is one that does offer a high precision date.
|
||||
If you are using Bash 5.0 or newer, there is a shell variable called e-pock-real-time
|
||||
that is capitals, E-P-O-C-H-R-E-A-L-T-I-M-E,
|
||||
which offers time since the Unix-E-Pock that is since the first January 1970 at zero zero zero UTC
|
||||
in seconds to six decimal of precision.
|
||||
Example, echo, dollar sign E-Pock-real-time
|
||||
will give the result of one seven seven nine six three four eight zero zero,
|
||||
decimal one eight four nine two six.
|
||||
This is related to the similar Bash variable known as e-pock-seconds that is capitals
|
||||
E-P-O-C-H-S-E-C-O-N-D-S, which gives the number of seconds since the Unix-E-Pock.
|
||||
Example, echo, dollar E-Pock-Seconds,
|
||||
to should print out an integer number somewhere along the lines of
|
||||
one seven seven nine six three four eight zero zero.
|
||||
So if you're using Bash, measuring time is very simple.
|
||||
Easier script, however, actually using Bash.
|
||||
Debian and derivatives actually have two shells.
|
||||
The first, the interactive shell is Bash.
|
||||
The second, the non-interactive shell is Dash, which stands for Debian,
|
||||
Almquist Shell.
|
||||
If you open a terminal, you get Bash.
|
||||
If your script starts with a bin Bash Shabangline, you get Bash.
|
||||
However, if your script starts with a bin S-H Shabangline,
|
||||
you get Dash that's D-A-S-H.
|
||||
Some people find themselves getting caught out by this one.
|
||||
When they try something out in a terminal,
|
||||
but find that it doesn't work in their script,
|
||||
which started with a bin S-H.
|
||||
Many other, but not all,
|
||||
Linux-Tistros use Bash for both the interactive
|
||||
and non-interactive shells, so bin S-H
|
||||
and bin Bash work the same with those ones.
|
||||
If you intend to use Bash,
|
||||
make sure your script calls for Bash in the first line.
|
||||
The shell variable.
|
||||
So how can a script tell what shell it is running under?
|
||||
There's a shell variable called shell that's capital S-H-E-L-L,
|
||||
which will tell you the name of the shell.
|
||||
Well, sort of.
|
||||
On Debian and derivatives, shell will say Bash,
|
||||
regardless of whether the actual shell is Bash or Dash.
|
||||
On some other operating systems, shell will simply say S-H,
|
||||
even if it is something else entirely.
|
||||
So we need to do some additional levels of checking
|
||||
to see what we have.
|
||||
To start with though, here's what each of the testistros reports for shell.
|
||||
Alma says Bash, alpine says S-H,
|
||||
Debian says Bash, free BSD says S-H,
|
||||
open BSD says K-S-H,
|
||||
Raspberry Pi says Bash, open Sous says Bash,
|
||||
Ubuntu says Bash.
|
||||
Bash versus Dash.
|
||||
First, let's try to see which ones are Bash
|
||||
and which ones are Dash.
|
||||
The first thing we can check for is the shell variable Bash version
|
||||
that is capital B-A-S-H underscore
|
||||
V-E-R-S-I-O-N.
|
||||
Example, echo, dollar sign, Bash version.
|
||||
If the shell is Bash, that it will report a version string.
|
||||
If the shell is not Bash, then it will return an empty value.
|
||||
Using this test, we can see that alma
|
||||
and open Sous are indeed using Bash.
|
||||
We however need to check Debian, Raspberry Pi, and Ubuntu,
|
||||
when running an S-H script.
|
||||
To check this, we can use the which command
|
||||
this W-H-I-C-H
|
||||
to see what S-H actually is.
|
||||
Example, echo, space, dollar sign,
|
||||
left bracket, L-S, space, dash L, space,
|
||||
dollar sign, left bracket, which space, S-H.
|
||||
Right bracket, space, pipe symbol, space, R-E-V, space,
|
||||
pipe symbol, space, cut that C-U-T, space, dash D,
|
||||
double quote, space, double quote, space,
|
||||
dash F-1, space, pipe symbol, space, cut, space,
|
||||
dash D, slash, space, dash F-1, space, pipe symbol, space,
|
||||
R-E-V, space, right bracket, which space, S-H
|
||||
shows us the path to S-H.
|
||||
However, this is a link, so we need to use
|
||||
L-S, space, dash L to find the actual executable.
|
||||
R-E-V reverses the string, cut, takes the first element,
|
||||
separated by spaces.
|
||||
The second cut takes the first element, separated by
|
||||
the slash characters.
|
||||
The final R-E-V takes that string and reverses it again
|
||||
to get it in the correct order.
|
||||
In the case of Debian, Raspberry Pi, and Ubuntu,
|
||||
it tells us that this is dash.
|
||||
Open B-S-D. Open B-S-D reports its shell as K-S-H,
|
||||
which stands for cornshell.
|
||||
It is indeed cornshell, so we can simply leave that one as is.
|
||||
Alpine and free B-S-D.
|
||||
Next, we have Alpine Linux and free B-S-D,
|
||||
which both report as S-H.
|
||||
In the case of free B-S-D, there doesn't appear to be any further we can go that I'm aware of.
|
||||
It's simply S-H.
|
||||
It is a basic posic shell, which seems to be similar to the original Unix shell,
|
||||
the born shell.
|
||||
Older versions of free B-S-D use a different shell,
|
||||
known as T-S-C-H, or the C shell.
|
||||
But I haven't tested that, so I will ignore that here.
|
||||
With Alpine Linux, however, we can get the actual shell
|
||||
using the same method that we use for Debian Linux.
|
||||
This report is being busy box.
|
||||
Busy box is a limited shell, intended for use in embedded systems.
|
||||
Alpine was originally an embedded digital,
|
||||
but some people started using it for containers.
|
||||
Alpine is Linux, but it is not GNU Linux,
|
||||
and there are a number of areas which can trip you up if you're not aware of them.
|
||||
So be extra careful if you're using it for anything
|
||||
and test everything.
|
||||
Summary of actual shells.
|
||||
Here is our revised list with the actual shell used,
|
||||
when asking for S-H, so far as we can determine.
|
||||
Alma uses bash, Alpine uses busy box,
|
||||
Debian uses dash, free B-S-D uses S-H,
|
||||
open B-S-D uses K-S-H,
|
||||
Raspberry Pi uses dash,
|
||||
Susa uses bash,
|
||||
and Ubuntu uses dash.
|
||||
There are other shells,
|
||||
but none of them are the default shell
|
||||
for any of the distros on our list,
|
||||
so I haven't tested them.
|
||||
Solutions for measuring time.
|
||||
Now we need to find solutions for bash,
|
||||
dash, K-S-H, S-H, and busy box.
|
||||
Bash.
|
||||
For bash, we can simply use apoc real-time,
|
||||
as mentioned above.
|
||||
Dash.
|
||||
For dash, we can use the date command.
|
||||
This is a very conventional method,
|
||||
and it's probably the first answer
|
||||
that anyone would give for this situation.
|
||||
However, while it will work in most cases,
|
||||
it will not work in all cases,
|
||||
so it is not a universal solution.
|
||||
To use date, we simply call it with the correct format string.
|
||||
This uses percent lower-case S to get
|
||||
seconds to see apoc,
|
||||
and percent upper-case N,
|
||||
to get nanoseconds of the current second.
|
||||
If you put a decimal separator between the two,
|
||||
it will appear in the output.
|
||||
You can use the correct decimal separator for your local,
|
||||
but it won't go into that here.
|
||||
Instead, I will just assume a period or dot.
|
||||
Example, date, space, single-quote,
|
||||
plus percent lower-case S, dot,
|
||||
percent upper-case N, single-quote.
|
||||
This will output a number,
|
||||
which is the number of seconds as apoc,
|
||||
and then a dot, and then the number of nanoseconds.
|
||||
Problems with date on alpine and open BSD.
|
||||
Date will work for bash, dash,
|
||||
and SH on free BSD.
|
||||
However, it will not work for KSH on open BSD,
|
||||
or for busybox on alpine.
|
||||
With busybox on alpine,
|
||||
it simply ignores the percent and format specifier,
|
||||
and prints out the apoc in seconds only,
|
||||
followed by the decimal separator.
|
||||
With KSH on open BSD,
|
||||
it prints the apoc in seconds,
|
||||
followed by the decimal separator,
|
||||
and then the percent N as a literal N.
|
||||
Fortunately, we have alternatives for these two cases.
|
||||
Open BSD.
|
||||
Open BSD has the TS or timestamp utility,
|
||||
installed by default.
|
||||
TS prints a timestamp in front of every line,
|
||||
it receives from standard input.
|
||||
I won't go into details on all aspects of TS here.
|
||||
I'll leave that to someone else.
|
||||
Instead, I will focus on how to use it for our specific purposes here.
|
||||
We need to provide a format specifier to TS,
|
||||
which in this case is,
|
||||
quote,
|
||||
percent dot,
|
||||
lowercase as quote.
|
||||
We also need to provide something for standard input,
|
||||
otherwise TS will simply sit there
|
||||
and wait for input.
|
||||
So what we need to do is to echo nothing through a piped TS,
|
||||
or also giving TS the proper format specifier.
|
||||
Example, echo, space, pipe symbol, space, TS, space,
|
||||
double quote, percent dot, lowercase as double quote.
|
||||
This will provide the apoc time in seconds to six decimals of precision.
|
||||
TS is installed in OpenBSD and FreeBSD by default,
|
||||
and can be used in either.
|
||||
It can also be installed in many other distros.
|
||||
Busybox on Alpine.
|
||||
None of the methods discussed so far
|
||||
will work for busybox on Alpine though.
|
||||
However, there is a way,
|
||||
but it's a bit non-obvious and somewhat hacky.
|
||||
Busybox includes a command called
|
||||
A-D-J-T-I-M-E-X,
|
||||
which I will pronounce adjust time X.
|
||||
This is normally used to adjust the time hardware.
|
||||
However, if it is run without arguments,
|
||||
it will report the current settings.
|
||||
These include the current apoc time in seconds,
|
||||
and in another field, the time in microseconds.
|
||||
These are reported as key value pairs.
|
||||
So what we need to do is the following.
|
||||
Run adjust time X as A-D-J-T-I-M-E-X,
|
||||
capture the output,
|
||||
grip for time dot TV underscore,
|
||||
SEC, grip for time dot TV,
|
||||
underscore U-S-E-C,
|
||||
use cut to extract the time value in each case,
|
||||
use TR to get rid of excess spaces in each case,
|
||||
combine the two in a string
|
||||
with a decimal separator between them.
|
||||
This takes a total of four lines of shell script.
|
||||
I will just describe them briefly here.
|
||||
See the show notes for details.
|
||||
First, we want to capture the output
|
||||
of adjust time X in a single operation.
|
||||
Run adjust time X
|
||||
and pipe the output through grip to capture lines containing
|
||||
TI-M-E dot TV underscore
|
||||
and save this to a variable.
|
||||
Next, echo the contents of this variable
|
||||
and pipe it through grip, cut, and TR
|
||||
to get first the seconds and then the microseconds
|
||||
will also removing excess spaces,
|
||||
save these to two separate variables.
|
||||
Adjust time X does not zero pass the microsecond time value
|
||||
to provide dating zeros.
|
||||
So we need to take care of this using printF
|
||||
before we can append it to the second value.
|
||||
We didn't need to do this with date
|
||||
where the percent n format character does this automatically.
|
||||
In this instance, the printF format string is single-quote
|
||||
percent zero-six D single-quote.
|
||||
Now, combine these into a single number
|
||||
with a decimal separator
|
||||
by using simple string concatenation.
|
||||
Summary of methods.
|
||||
Let's summarize where we are so far in terms of methods
|
||||
that we can use to get the current time
|
||||
as a high-resolution number for Alma
|
||||
or Debian with Bash or Raspberry Pi with Bash
|
||||
or Ubuntu with Bash
|
||||
or Susa use apocryl time or date.
|
||||
For Debian with Bash or Raspberry Pi with Bash
|
||||
or Ubuntu with Bash use date.
|
||||
For Alpine, use Adjust Time X
|
||||
and Parsee output.
|
||||
For FreeBSD use date or TS
|
||||
for OpenBSD use TS.
|
||||
Other alternatives,
|
||||
through a few alternatives that we haven't discussed yet.
|
||||
Bash with Bash.
|
||||
In the case of Debian, Raspberry Pi and Ubuntu running dash,
|
||||
since Bash is available,
|
||||
it is possible to write a separate Bash script
|
||||
which simply echoes apocryl time
|
||||
and then call it from the dash script
|
||||
and capture the output.
|
||||
Well, this would work,
|
||||
there's probably not a lot of point to it.
|
||||
If you can rely on Bash being there,
|
||||
then just change the first line to the script
|
||||
and make it a Bash script.
|
||||
Adding Packages to Alpine.
|
||||
The TS or Time Stamp Utility
|
||||
is a common unix utility
|
||||
that can be installed if it is not present by default.
|
||||
This does produce high-resolution timestamps on Alpine.
|
||||
On Alpine Linux, this comes as part of the more utils package
|
||||
to add the package used the following.
|
||||
Suedu, APK, add more utils.
|
||||
You can also add the GNU Core Utils,
|
||||
which will provide a high-resolution date command,
|
||||
which works like in the other examples.
|
||||
To add the package, use the following.
|
||||
Suedu, APK, add Core Utils.
|
||||
If you can install more packages into your Alpine system,
|
||||
either of the above two is probably going to be preferable
|
||||
to parsing the output of adjust time X.
|
||||
Custom Time Stamp Programs.
|
||||
You could also write a very short program in Python,
|
||||
Pearl, tickle, or some other language,
|
||||
and have it output the current APK time.
|
||||
I won't discuss that here though.
|
||||
Calculating time differences.
|
||||
Scholar arithmetic isn't a juror only.
|
||||
If we wish to use high-resolution timing data,
|
||||
we need to do something so that we don't lose a precision
|
||||
we have work so hard to get.
|
||||
There are several possible solutions.
|
||||
Change the time base.
|
||||
One method is to change the time base from seconds to
|
||||
Billy, Micro, or Nano seconds.
|
||||
This can be done by simply multiplying the time values
|
||||
by the appropriate amount, say a thousand, or million, etc.
|
||||
Before subtracting them.
|
||||
This allows for integer arithmetic on high-resolution values
|
||||
without losing precision.
|
||||
Use the shell, BC, Herbatory Precision Calculator.
|
||||
The BC command line calculator will perform calculations using real numbers
|
||||
and is easy to use in scripts.
|
||||
It is present by default in most districts.
|
||||
For example, echo, space, double-quote, scale equals nine,
|
||||
semicolon, space, dollar sign, end time, space, minus space,
|
||||
dollar sign, start time, double-quote, space, pipe symbol, space, BC.
|
||||
Where end time and start time are variables containing time values.
|
||||
However, for some inexplicable reason, neither debion
|
||||
nor open sues installed by default.
|
||||
It is present in Ubuntu and Raspberry Pi which are debion derivatives
|
||||
and it can be added to districts which lack it.
|
||||
Use, oc. oc can also perform calculations using real numbers
|
||||
and it is present in dearly all districts including
|
||||
and all of the ones we tested here.
|
||||
Here's an example.
|
||||
echo, space, double-quote, dollar sign, end time, space,
|
||||
dollar sign, start time, double-quote, space, pipe symbol, space, oc.
|
||||
That's a WK, space, single-quote, curly bracket,
|
||||
print-f, space, double-quote, percent, dot, six, f,
|
||||
back slash, end, double-quote, comma, space, dollar sign,
|
||||
one, space, minus space, dollar sign, two, right curly bracket, single-quote.
|
||||
benchmarks.
|
||||
And of course, no comparative evaluation would be complete
|
||||
with a benchmarks where we can see how each method compares to another in terms of speed.
|
||||
In the benchmark test, I ran each method in the loop through multiple iterations,
|
||||
measured the laps time, subtracted out the time from an empty loop,
|
||||
and then compared it to the alternate methods.
|
||||
For anything other than apoc real time, the empty loop time is negligible
|
||||
and has no really fact on the results.
|
||||
Rather interestingly, I came across a bug which caused date to run very slowly
|
||||
called immediately after using apoc real time in bash.
|
||||
The effect of the bug was to make the date benchmark test run roughly 24 times slower.
|
||||
This has been fixed in newer releases, but if you're using an older gesture release,
|
||||
then be aware of this bug.
|
||||
I was able to get around it by either putting a sleep delay between benchmarking apoc real time
|
||||
and benchmarking date, or by simply testing date before testing apoc real time.
|
||||
To be able to conduct additional tests, I installed TS in Ubuntu and Alpine
|
||||
and the GNU version of date in Alpine.
|
||||
Apoc real time versus date in Ubuntu 2604 bash.
|
||||
The apoc real time method is 3100 and 3 times faster than just date.
|
||||
However, when the same test is run on Ubuntu 2404, when the date test is run before
|
||||
the apoc real time test, apoc real time is 1,240 times faster than date.
|
||||
Other Linux test rows show performance similar to Ubuntu 2404.
|
||||
It appears that a side effect of fixing whatever the bug is as the effect of slowing down date.
|
||||
However, this is probably not a significant issue in normal circumstances.
|
||||
Date versus TS in Ubuntu 2604 bash.
|
||||
The date method is 3.7 times faster than TS.
|
||||
Date versus TS in Ubuntu 2604 dash.
|
||||
The date method is 4.9 times faster than TS.
|
||||
Date versus TS in free BSD as H.
|
||||
The date method is 2.5 times faster than TS.
|
||||
Date versus adjust time X and Alpine busy box.
|
||||
The date method is 6 times faster than adjust time X.
|
||||
Date versus TS and Alpine busy box.
|
||||
The date method is 20 times faster than TS.
|
||||
B.C. versus Ock. It Ubuntu 2604.
|
||||
I compare calculating the difference between the two numbers when using B.C. versus Ock.
|
||||
The difference is negligible with B.C. being only 7% faster than Ock.
|
||||
Conclusion for benchmarks. Based on these results, if you need to measure elapsed time to high resolution
|
||||
and care about running the command with as little overhead as possible,
|
||||
then the order preference should be the following.
|
||||
If you're using a newer version of bash, then use a Pock real time.
|
||||
If that is not available, then use date provided it allows for high resolution times.
|
||||
If the above two cannot be used, then use TS.
|
||||
If you're using busy box and cannot install either GNU data or TS,
|
||||
then use adjust time X.
|
||||
Date is a closest in terms of being the universal portable solution,
|
||||
but it does not work in all cases.
|
||||
I have not compared different platforms to each other in terms of performance,
|
||||
as that would be a much more involved problem that is outside the scope of this episode.
|
||||
However, different operating systems implement commands in different ways.
|
||||
For example, on OpenBSD and FreeBSD, TS appears to be an elf binary.
|
||||
That is, it is executable machine code, possibly written in C.
|
||||
On Ubuntu, however, TS appears to be a Pearl script.
|
||||
As a result of this, the advantage that date has over TS is much less in FreeBSD
|
||||
than it is with Ubuntu and likely other Linux tutorials.
|
||||
As on FreeBSD, it doesn't need to load a Pearl interpreter to run TS.
|
||||
Overall, conclusion.
|
||||
You know, don't thought that measuring elapsed time was going to be so simple
|
||||
and how could someone get an entire podcast out of such a simple subject.
|
||||
And yet here we are half an hour later, with just a basic overview of the subject.
|
||||
I hope you found this interesting and informative.
|
||||
Please let us know in the comments if you think that I have done anything incorrectly,
|
||||
or if you have another way of doing things.
|
||||
I hope to see you all again in another future episode of HPR.
|
||||
You have been listening to the Hacker Public Radio podcast, at hackerpublicradio.org.
|
||||
Today's show was contributed by a HPR listener like yourself.
|
||||
If you ever thought of recording a podcast, then visit the HPR site to find out how easy it really is.
|
||||
Hosting for HPR has been kindly provided by anhonesthost.com, the Internet Archive, rsync.net, and the HPR Community Content Delivery Network.
|
||||
Unless otherwise stated, today's show is released under a Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.
|
||||
Reference in New Issue
Block a user