How can I detect BSD vs. GNU version of date in shell script
Asked Answered
P

4

29

I am writing a shell script that needs to do some date string manipulation. The script should work across as many *nix variants as possible, so I need to handle situations where the machine might have the BSD or the GNU version of date.

What would be the most elegant way to test for the OS type, so I can send the correct date flags?

EDIT: To clarify, my goal is to use date's relative date calculation tools which seem distinct in BSD and GNU.

BSD example

date -v -1d

GNU example

date --date="1 day ago"
Ponton answered 5/1, 2012 at 18:35 Comment(2)
Detecting the OS type is useless, as gnu date may be installed on a BSD, or uninstalled from a GNU-Linux box.Vachil
OK, that's useful to know. I should have been clearer. My goal is to do relative date calculation, which (I think) in GNU is done with -v and in BSD is slightly different but with --date. Is there a common way to do these date subtractions?Ponton
V
14

Use portable flags. The standard is available here

For the particular problem of printing a relative date, it is probably easier to use perl than date:

perl -E 'say scalar localtime(time - 86400)'

(note that this solution utterly fails on 23 or 25 hour days, but many perl solutions are available to address that problem. See the perl faq.)

but you could certainly use a variation of Keith's idea and do:

if date -v -1d > /dev/null 2>&1; then
  DATE='date -v -1d'
else
  DATE='date --date="1 day ago"'
fi
eval "$DATE"

or just

DATE=$(date -v -1d 2> /dev/null) || DATE=$(date --date="1 day ago")

Another idea is to define a function to use:

if date -v -1d > /dev/null 2>&1; then
    date1d() { date -v -1d; }
else
    date1d() { date --date='1 day ago'; }
fi
Vachil answered 5/1, 2012 at 19:1 Comment(7)
Funny this should come up in a discussion about portable standards, but perl -E 'say "blah"' doesn't work for me, so I had to use perl -e 'print "blah\n"'Anthracite
-E is available in 5.12, but not in 5.8. (Don't recall exactly when it becomes valid, probably in the 5.10 series.)Vachil
your "or just" sends BSD output to /dev/null, try DATE=$(date -v 1d 2>/dev/null || date --date="1 day ago")Netsuke
@Netsuke Thanks! Cut-n-paste errors gone rampant. EditedVachil
You are missing a hyphen before the 1d, otherwise you will get the 1st day of the current month.Brahmani
So the only portable flag is -u? That's pretty limiting.Ferromagnetism
PHP is also a nice alternative since strtotime() has human-friendly syntax, e.g. php -r "echo date('Y-m-d', strtotime('-1 day'));"Nawrocki
T
20

You want to detect what version of the date command you're using, not necessarily the OS version.

The GNU Coreutils date command accepts the --version option; other versions do not:

if date --version >/dev/null 2>&1 ; then
    echo Using GNU date
else
    echo Not using GNU date
fi

But as William Pursell suggests, if at all possible you should just use functionality common to both.

(I think the options available for GNU date are pretty much a superset of those available for the BSD version; if that's the case, then code that assumes the BSD version should work with the GNU version.)

Tyrr answered 5/1, 2012 at 19:13 Comment(4)
+1: this is a good approach, but I would go a bit further. If there is a particular option that you worry may not be available, try it: if it fails then remove it from the option string you pass to date. This very quickly becomes very more tedious than just using perl.Vachil
@WilliamPursell: It's hard to imagine a case where you want to use a certain option but can get along without it. Maybe if you want to format the date in a certain way, but you can fall back to the default if it's not available (which makes sense only if the output is meant to be human-readable). But that's the implicit assumption behind the question, so ...Tyrr
The BSD includes some flags that are not available on the GNU, and vice versa. For instance, to convert from UTC, BSD needs the -r flag, which directs to a file for GNU. GNU can use -d for this task, which does not exist on BSD. It may be possible to convert from UTC on both...but I have not found it.Pulsimeter
The question explicitly mentions -v and --date, neither of which are shared with the other version and which have different syntaxes.Ferromagnetism
V
14

Use portable flags. The standard is available here

For the particular problem of printing a relative date, it is probably easier to use perl than date:

perl -E 'say scalar localtime(time - 86400)'

(note that this solution utterly fails on 23 or 25 hour days, but many perl solutions are available to address that problem. See the perl faq.)

but you could certainly use a variation of Keith's idea and do:

if date -v -1d > /dev/null 2>&1; then
  DATE='date -v -1d'
else
  DATE='date --date="1 day ago"'
fi
eval "$DATE"

or just

DATE=$(date -v -1d 2> /dev/null) || DATE=$(date --date="1 day ago")

Another idea is to define a function to use:

if date -v -1d > /dev/null 2>&1; then
    date1d() { date -v -1d; }
else
    date1d() { date --date='1 day ago'; }
fi
Vachil answered 5/1, 2012 at 19:1 Comment(7)
Funny this should come up in a discussion about portable standards, but perl -E 'say "blah"' doesn't work for me, so I had to use perl -e 'print "blah\n"'Anthracite
-E is available in 5.12, but not in 5.8. (Don't recall exactly when it becomes valid, probably in the 5.10 series.)Vachil
your "or just" sends BSD output to /dev/null, try DATE=$(date -v 1d 2>/dev/null || date --date="1 day ago")Netsuke
@Netsuke Thanks! Cut-n-paste errors gone rampant. EditedVachil
You are missing a hyphen before the 1d, otherwise you will get the 1st day of the current month.Brahmani
So the only portable flag is -u? That's pretty limiting.Ferromagnetism
PHP is also a nice alternative since strtotime() has human-friendly syntax, e.g. php -r "echo date('Y-m-d', strtotime('-1 day'));"Nawrocki
T
2

It is recommended to use feature-based defection rather than the os-based or platform-based detection.

Following sample code show how it works:

if is-compatible-date-v; then
    date -v -1d
elif is-compatible-date-d; then
    date --date="1 day ago"
fi
function is-compatible () {
    "$@" >/dev/null 2>&1
}

function is-compatible-date-v () {
    is-compatible date -v +1S
}

function is-compatible-date-d () {
    is-compatible date -d '1 second'
}

I was having the exact need to writing some shellcode to deal with date, and hoping the code be able to run on both BSD and GNU version of date.

I end up with a set of scripts that provides a uniform interface for both BSD and GNU version of date.

Example:

Follow command will output a date that is 21 days later than 2008-10-10, and it works with both BSD and GNU version of date.

$ xsh x/date/adjust +21d 2008-10-10
2008-10-31

The scripts can be found at:

It's a part of a library called xsh-lib/core. To use them you need both repos xsh and xsh-lib/core, I list them below:

Hope this will help people with the same need.

Taeniacide answered 30/1, 2021 at 11:47 Comment(0)
H
1

To just answer your question, probably "uname -o" is what you want. In my case it's "GNU/Linux". You can decide for yourself if detecting the os type is worthless or not.

Hizar answered 5/1, 2012 at 20:44 Comment(1)
BSD's uname doesn't accept an -o flag. Portability is harder that just trying it on your system and assuming it will work elsewhere. In this case you can use the -s flag instead.Capsulate

© 2022 - 2025 — McMap. All rights reserved.