12

How can I safely get the version of ksh from within a ksh script?

I have seen the following solutions:

  1. ksh --version
  2. echo ${.sh.version}
  3. echo $KSH_VERSION

And given the right circumstances, each of these works correctly. However, I care about the non-perfect case.

Specifically, there are several machines I work with that have older versions of ksh that, for my purposes, are severely lacking in functionality. Anyway, the reason I want to check the version (programmatically) is to see if the ksh version is one of the less capable versions; and if so, I want to execute a branch with less awesome code.

However, on the problematic machines, the shell's ineptitude extends into checking the version...

  • If I try ksh --version, it prints nothing and opens a new instance of ksh!
  • If I try echo ${.sh.version}, ksh treats this as a syntax error that cannot be discarded with 2> /dev/null.

    $ echo ${.sh.version} 2> /dev/null  
    ksh: ${.sh.version}: bad substitution
    
  • Of course echo $KSH_VERSION appears to work fine – I mean it won't crash – though on these machines it's blank. Also, I saw somewhere that KSH_VERSION is set only by pdksh.

Questions:

  • How can I safely check the version of ksh programmatically? For my purposes here, I don't really care what the actual version number is, just whether it's an outdated version of ksh.
  • Is $KSH_VERSION good enough? I mean if it's blank, then is ksh necessarily an outdated version? Was that other forum correct that it might not be set even for newer versions of ksh?
  • Is there just no way to check this at all?
  • 1
    Any reason you want two code paths and not just a single one with less awesome code? – Thorbjørn Ravn Andersen May 2 '15 at 7:33
  • @ThorbjørnRavnAndersen it has to do with the prompt. In my .kshrc file, I have a function that simulates the pwd-abbreviating functionality of tcsh and zsh prompts, and I set up PS1 to use this function. However, Old ksh does not support $() in PS1. So if it's a modern version of ksh, I want PS1 to use the function I created; if it's the old version, I use just $PWD. – Sildoreth May 4 '15 at 12:44
  • Well, you could have two versions of your configuration file (perhaps one generated from the other) and then distribute out the appropriate version to the machine in question? – Thorbjørn Ravn Andersen May 4 '15 at 19:25
  • Another approach could be to simply say "It is only this particular machine which has the problem - I'll find a file or environment variable or something else which only exists here (probably AIX or something anyway) and test for that instead". – Thorbjørn Ravn Andersen May 4 '15 at 19:26
7

I think that .sh.version has existed ever since the first version of ATT ksh 93. It isn't available in pdksh or mksh. Since ${.sh.version} is a syntax error in shells other than ksh93, wrap the test for it in a subshell and protect it behind eval.

_sh_version=$(eval 'echo "${.sh.version}"' 2>/dev/null) 2>/dev/null
case $_sh_version in
  '') echo "This isn't ATT ksh93";;
  …
esac

KSH_VERSION started out in the public domain ksh clone (pdksh), and was added to the actual Korn shell relatively recently, in 2008 with ksh93t.

Rather than test for a version number, you should test for the specific feature that's giving you grief. Most features can be tested for by trying some construct in a subshell and see if it triggers an error.

  • I don't see any difference when using a subshell. It still treats ${.sh.version} as a syntax error that cannot be reconciled. The message I get is bad substitution. – Sildoreth May 4 '15 at 19:50
  • @sil The point of using a subshell is to catch the error. Redirect errors to /dev/null and ignore the exit status. – Gilles May 4 '15 at 22:02
  • I understand what you're saying. What I'm saying is that the error doesn't redirect. It always prints to the console. I tried this in Solaris, AIX, and HP-UX; and ksh exhibits this behavior in all of them. – Sildoreth May 5 '15 at 12:49
  • @Sildoreth Ah. I'd only tested on Linux, and I don't have any of these OSes to test now. Does eval '_sh_version=$(echo "${.sh.version}")' 2>/dev/null work any better? – Gilles May 5 '15 at 13:19
  • That's a little better. It works prefectly in Solaris and HP-UX. For AIX, it works at the command line but curiously starts failing again if I try to place it in a shell function. – Sildoreth May 5 '15 at 13:41
6

KSH_VERSION was not implemented in ksh93 before version 93t. It will be set in mksh, pdksh, lksh. So for checking the version of ksh, we can try these steps:

  • Checking KSH_VERSION to detect mksh, pdksh, lksh
  • If first step fails, try a feature that's different between ksh93 and ksh88/86 (Let David Korn show us).

With these in mind, I will go with:

case "$KSH_VERSION" in
  (*MIRBSD*|*PD*|*LEGACY*) printf '%s\n' "$KSH_VERSION" ;;
  (*) [ -z "$ERRNO" ] && printf '%s\n' "${.sh.version}" || echo ksh88/86 ;;
esac
  • Why doesn't this check if $KSH_VERSION is non-blank first? On my Ubuntu machine, this prints "ksh93", yet KSH_VERSION is set. – Sildoreth May 1 '15 at 19:39
  • This would fail if some previously executed code (eg: .kshrc) tampered the KSH_VERSION variable with some random value. – jlliagre May 2 '15 at 10:03
  • @jlliagre: No, as it was run as a script, it don't read .kshrc. – cuonglm May 2 '15 at 10:23
  • If the ENV variable is set (and typically it is set to ~/.kshrc), the script will definitely read the .kshrc file. Of course, it would be quite odd for a script to set a bogus KSH_VERSION but this is nevertheless possible, just like explicitly executing a script with a different interpreter than the one specified in its first line is a possible situation. – jlliagre May 2 '15 at 13:42
  • @jlliagre: Even you can change it, you will get segfault when you reference to KSH_VERSION. And in mksh, pdksh, lksh, KSH_VERSION is marked as readonly. – cuonglm May 2 '15 at 15:54
5

For "real" ksh releases (i.e. AT&T based), I use this command:

strings /bin/ksh | grep Version | tail -2 

Here are various output I get:

Original ksh:

@(#)Version M-11/16/88i

dtksh;

@(#)Version 12/28/93
Version not defined

Modern ksh93:

@(#)$Id: Version AJM 93u+ 2012-08-01 $

For pdksh/msh ksh clones and modern AT&T ksh versions too, here is something that works:

$ mksh -c 'echo $KSH_VERSION'
@(#)MIRBSD KSH R50 2015/04/19

Edit:

I overlooked you were asking about doing it from inside a script, not by knowing the path to the tested ksh binary.

Assuming you really want the version of ksh used, and not the features it supports, here is one way to do it using only the strings command that should work on at least on Linux and Solaris:

echo $(for i in $(find /proc/$$ ! -type d ! -name "pagemap" | 
  grep -v "/path/" | grep -v "/fd/" ) ; do
  strings $i | egrep "([V]ersion|[K]SH_VERSION).*[0-9]" | sort -u
done 2>/dev/null)

Note that this method is unreliable as /proc might not be mounted, and there are certainly other weaknesses. It is untested on other Unix OSes.

  • This won't be distinguish between lksh and pdksh in Debian Jessie. – cuonglm May 1 '15 at 20:01
  • @cuonglm I have no Jessie to test. Do you mean lksh and pdksh can't be sorted out from their KSH_VERSION? – jlliagre May 1 '15 at 20:08
  • No, I mean running strings on them. KSH_VERSION definitively can. – cuonglm May 1 '15 at 20:12
  • @cuonglm Sorry if I was unclear. When I wrote « for "real" ksh releases », I was explicitly excluding non AT&T ksh clones like pdksh, mksh and lksh. – jlliagre May 1 '15 at 20:16
  • Running strings on some ksh binary is a bad idea because you don't know if that's the one that's running your script. Maybe your script is being run by /usr/local/bin/ksh or /home/bob/bin/ksh or /bin/sh or /usr/posix/bin/sh or … – Gilles May 1 '15 at 22:04
2

While I was writing a script for ksh, I noticed that the -a option of ksh's built-in whence command appears to not be supported in older versions of ksh. And this appears to be true on all the systems I checked, which included Solaris, AIX, HP-UX, and Linux.

So here is the solution as a ksh function:

is_modern_ksh() {
  if whence -a whence > /dev/null 2>&1 ; then
    return 0 #success -> true
  fi
  #Else the call to `whence` failed because `-a` is not supported
  return 1 #failure -> false
}

And here's how to use it:

if is_modern_ksh ; then
  echo "You're using a MODERN version of ksh. :)"
else
  echo "You're using an OLD version of ksh. :("
fi
  • Why don't you use ${.sh.version}? – cuonglm May 6 '15 at 16:59
  • @cuonglm because I can't. See the comments on Gilles' answer. – Sildoreth May 6 '15 at 17:11
  • unfortunately the whence in Zsh has -a – Greg A. Woods Aug 3 '17 at 1:59
  • @GregA.Woods, this function is specifically for ksh. The function definition would go in .kshrc and so would not even exist for other shells such as zsh. zsh has its own built-in whence command which is in no way tied to ksh or the version thereof. I don't even know why you'd care to check if ksh is an old version from within an instance of zsh, which is a completely different shell. – Sildoreth Aug 3 '17 at 13:59
  • There is a problem with your assumptions: Zsh is often installed with a link to /bin/ksh, e.g. on Debian Linux. Now I don't use it there (and at the moment I can't change my login shell to check), so I don't know if it reads .kshrc or not, but I would suspect it does. – Greg A. Woods Aug 3 '17 at 18:40
1

CTRL+ALT+V

or

ESC , CTRL+V

Have typically proven very reliable as far as interactively determining the version of KSH you're using, however scripting them has proven more difficult.

  • 1
    this was the only one that worked for an AIX ksh 88f version. – Jeff Schaller Apr 26 '17 at 13:04
  • 1
    I got the <kbd>ESC</kbd> , <kbd>CTRL</kbd>+<kbd>V</kbd> option to work, after I ran set -o vi to set the keybindings to be vi-like. Before that, or with +o vi or -o emacs, it simply would not show me. PD KSH v5.2.14 99/07/13.2 on openbsd 6.1 – bgStack15 Jul 26 '17 at 22:37
0

I think the fundamental problem with using ${.sh.version} is that ksh88 just stops, with a non-zero exit code.

So my solution is to put the code that references ${.sh.version} in a sub-shell, then test to see if the sub-shell exits non-zero and have code in the sub-shell that will work on versions of the ksh where referencing ${.sh.version} does work. Wrapping it into a function that is then called by another function that reverses the return code, so that the final call is checking for true.

function is_oldksh
{
    (test -n ${.sh.version}) 2>/dev/null
}

function oldkshtest
{

    is_oldksh || return 0 && return 1
}

oldkshtest && echo "old ksh" || echo "new ksh"

I have run this on AIX and Oracle Enterprise Linux 5 & 6, with ksh88, ksh93 and pdksh.

Pete

  • 1
    modern AT&T Ksh still supplies .sh.version (indeed KSH_VERSION is effectively an alias for it). Also some shells, e.g. NetBSD sh, simply stop reading after encountering ${.sh.version} and no amount of redirection can keep them running the script. – Greg A. Woods Aug 3 '17 at 2:06
0

The following seems to work reasonably well for all the shells I've tested, including old ksh88e and a nearly complete range of common Ksh clones (though only one version of each), though I've not yet tested an actual original Bourne shell (and doing so may require adapting the test expression for older versions....

Addendum:

I've now also successfully tested this with Heirloom Bourne Shell, although with an external (and more modern) test program.

is_attksh()
{
    # ksh93
    _sh_version=$(eval 'echo "${.sh.version}"' 2>/dev/null)
    # pdksh only
    _opt_login=$(set -o | grep login)

    test -n "${_sh_version}" -o \( -z "${_opt_login}" -a -n "${_}" -a -n "${ERRNO}" -a -n "${FCEDIT}" -a -n "${PS3}" \)
}
is_attksh && echo "AT&T Ksh${_sh_version:+: }${_sh_version:- (probably ksh88 or ksh86)}" || echo "not real ksh"

is_zsh()
{
    test -n "${ZSH_VERSION}"
}
is_zsh && echo "Zsh: ${ZSH_VERSION}" || echo "not zsh"
  • Why would you want to run this function for shells that aren't ksh? If you're running a script in bash or zsh, then ksh never comes into play. Furthermore, it's already been established via others' answers that ${.sh.version} cannot be part of the solution because certain versions of ksh – the versions that the original post was concerned about – error fatally on that syntax. – Sildoreth Aug 7 '17 at 13:58
  • As I said, the function I show has been tested with versions of ksh that give "fatal" errors, as well as versions of Ash that do the same. – Greg A. Woods Aug 7 '17 at 18:48
  • Scripts I write are intended to be portable and to be run by any capable shell. Also, as I said elsewhere, some folks won't necessarily know they're using Zsh as Ksh because when they type 'ksh' the Zsh binary will be invoked (with argv[0] as "ksh"). – Greg A. Woods Aug 7 '17 at 18:50
  • That does shed light on where you're coming from. However, that sounds like an unrealistic requirement. Typically, when a Unix developer says "portable", they don't mean "this code will run in any shell", they mean "this will run on any system". And if you need to execute a script that was written for another shell, that is perfectly legal; just start a non-interactive instance of the other shell in your script. I bring this up because I want to promote good coding practices. If this solution works for you, great. But I would advise others to take a simpler approach. – Sildoreth Aug 7 '17 at 19:10
  • 1
    Admittedly any efforts to drag backwards compatibility too far into the past are rather silly. I've only compiled versions of ancient AT&T Ksh and Unix Sh to satisfy my own personal desire to better understand the history and evolution of some features and to refresh my memory of how things were (which usually surprises me, as things were often much "better" than I remember, though sometime they were also much worse). – Greg A. Woods Aug 7 '17 at 19:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.