How can I safely get the version of ksh?

24,899

Solution 1

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

Solution 2

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

Solution 3

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.

Solution 4

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.

Solution 5

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.

Share:
24,899

Related videos on Youtube

Sildoreth
Author by

Sildoreth

I program for a living, and I program for fun.

Updated on September 18, 2022

Comments

  • Sildoreth
    Sildoreth over 1 year

    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?
    • Thorbjørn Ravn Andersen
      Thorbjørn Ravn Andersen almost 9 years
      Any reason you want two code paths and not just a single one with less awesome code?
    • Sildoreth
      Sildoreth almost 9 years
      @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.
    • Thorbjørn Ravn Andersen
      Thorbjørn Ravn Andersen almost 9 years
      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
      Thorbjørn Ravn Andersen almost 9 years
      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".
  • Sildoreth
    Sildoreth almost 9 years
    Why doesn't this check if $KSH_VERSION is non-blank first? On my Ubuntu machine, this prints "ksh93", yet KSH_VERSION is set.
  • cuonglm
    cuonglm almost 9 years
    This won't be distinguish between lksh and pdksh in Debian Jessie.
  • jlliagre
    jlliagre almost 9 years
    @cuonglm I have no Jessie to test. Do you mean lksh and pdksh can't be sorted out from their KSH_VERSION?
  • cuonglm
    cuonglm almost 9 years
    No, I mean running strings on them. KSH_VERSION definitively can.
  • jlliagre
    jlliagre almost 9 years
    @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.
  • Sildoreth
    Sildoreth almost 9 years
    @Gilles what if this was performed on the value returned by command -v $0?
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 9 years
    @Sildoreth How is $0 useful here? It designates the script, not to the interpreter.
  • Sildoreth
    Sildoreth almost 9 years
    @Gilles Oh. I was thinking .kshrc. But I see what you mean.
  • jlliagre
    jlliagre almost 9 years
    @cuonglm I updated my reply with a method that works with the strings command with lksh and pdksh too.
  • cuonglm
    cuonglm almost 9 years
    @jlliagre: If script was called with ksh script or lksh script or pdksh script, your solution failed.
  • jlliagre
    jlliagre almost 9 years
    @cuonglm Granted, indeed. One cannot reliably use the interpreter line, answer updated.
  • jlliagre
    jlliagre almost 9 years
    This would fail if some previously executed code (eg: .kshrc) tampered the KSH_VERSION variable with some random value.
  • cuonglm
    cuonglm almost 9 years
    @jlliagre: No, as it was run as a script, it don't read .kshrc.
  • jlliagre
    jlliagre almost 9 years
    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.
  • cuonglm
    cuonglm almost 9 years
    @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.
  • jlliagre
    jlliagre almost 9 years
    pdksh and derived shells are indeed protected by having the KSH_VERSION variable readonly but AT&T ksh ones are not. Releases older than ksh93t (2008) don't set it and accept it as a variable name with no restriction. Any script based on KSH_VERSION can easily be fooled if that variable has previously been exported or is set in a .kshrc file. ksh93t and newer still do not segfault if this variable is set by an initialization script. ksh93t to ksh93u only segfault it the variable is already set in their starting environment. Segfaulting was a bug that has been fixed in ksh93v.
  • Sildoreth
    Sildoreth almost 9 years
    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.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 9 years
    @sil The point of using a subshell is to catch the error. Redirect errors to /dev/null and ignore the exit status.
  • Sildoreth
    Sildoreth almost 9 years
    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.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' almost 9 years
    @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?
  • Sildoreth
    Sildoreth almost 9 years
    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.
  • cuonglm
    cuonglm almost 9 years
    Why don't you use ${.sh.version}?
  • Sildoreth
    Sildoreth almost 9 years
    @cuonglm because I can't. See the comments on Gilles' answer.
  • SailorCire
    SailorCire almost 9 years
    I haven't seen strings used in a while. It is a good program. Thanks for reminding me of it!
  • Stéphane Chazelas
    Stéphane Chazelas over 8 years
    Better as command eval '_sh_version=${.sh.version}' 2> /dev/null or { _sh_version=...; } 2> /dev/null
  • Jeff Schaller
    Jeff Schaller about 7 years
    this was the only one that worked for an AIX ksh 88f version.
  • bgStack15
    bgStack15 almost 7 years
    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
  • Greg A. Woods
    Greg A. Woods over 6 years
    Unfortunately NetBSD sh (though not dash-0.5.7-3 on linux) will outright stop reading the script when it sees ${.sh.version} with the complaint Syntax error: Bad substitution.
  • Greg A. Woods
    Greg A. Woods over 6 years
    unfortunately the whence in Zsh has -a
  • Greg A. Woods
    Greg A. Woods over 6 years
    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.
  • Gilles 'SO- stop being evil'
    Gilles 'SO- stop being evil' over 6 years
    @GregA.Woods Ouch. Can you try _sh_version=$(echo "${.sh.version}"; true) 2>/dev/null or _sh_version=$(eval 'echo "${.sh.version}"') 2>/dev/null ?
  • Sildoreth
    Sildoreth over 6 years
    @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.
  • Greg A. Woods
    Greg A. Woods over 6 years
    eval to the rescue again! The redirection has to be moved into the command expansion though: _sh_version=$(eval 'echo "${.sh.version}"' 2>/dev/null) Thank you very much!
  • Greg A. Woods
    Greg A. Woods over 6 years
    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
    Greg A. Woods over 6 years
    FYI, my eval version even works with ksh88e (which I just got to build and run on NetBSD/amd64, albeit with some gross hacks and minor fixes). I.e. it "works" in the sense that it leaves _sh_version empty and does not write anything to standard error; since as you know that version and those before it didn't have any way other than an editor command to show their version identification.
  • Stéphane Chazelas
    Stéphane Chazelas over 6 years
    Why the second 2> /dev/null?
  • Sildoreth
    Sildoreth over 6 years
    @Greg do you mean that "zsh" is set as a symbolic link to run ksh? If so, then you're not running zsh at all. And if that version of ksh supports -a, then that necessarily means that it's a modern version of ksh; and the is_modern_ksh function is just doing its job.
  • Sildoreth
    Sildoreth over 6 years
    @Greg Or do you mean that zsh calls into ksh for some of its functionality? If so, I doubt you even need this function. My main reason for needing a function like this was the need to know when to work around a lack of specific functionality in old versions of ksh. Is there a specific shortcoming of old ksh versions that is bleeding into zsh for you? If so, I'd be surprised since the founding principle of zsh is that it combines the best of all other shells while discarding all of the shortcomings.
  • Greg A. Woods
    Greg A. Woods over 6 years
    No, Zsh pretends to be Ksh, though until the most recent releases it did so rather poorly in some situations.
  • Sildoreth
    Sildoreth over 6 years
    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.
  • Greg A. Woods
    Greg A. Woods over 6 years
    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
    Greg A. Woods over 6 years
    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").
  • Sildoreth
    Sildoreth over 6 years
    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.
  • Greg A. Woods
    Greg A. Woods over 6 years
    I've been writing portable shell scripts that are intended to work with any Bourne (and later POSIX) compatible /bin/sh for the past 28 years. :-)
  • Greg A. Woods
    Greg A. Woods over 6 years
    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).
  • Michael Brux
    Michael Brux about 6 years
    Does not work with mksh: mksh: whence: -a: unknown option. KSH_VERSION is: @(#)MIRBSD KSH R54 2016/11/11 (on Debian 9 / stretch)
  • Sildoreth
    Sildoreth about 6 years
    @MichaelBrux The whole point is for an error to be thrown for older versions, which the error redirection then consumes. That's how this function identifies the ksh version as "old". Are you saying that the error redirection isn't working?
  • Michael Brux
    Michael Brux about 6 years
    @Sildoreth: "mksh is a successor of pdksh" - means, from my understanding, it is modern. Your code returns "OLD version", what is an error, from my point of view. Reason for this error is: mksha whence builtin has no -a option. Is that more clear / understandable / correct?