5

Is there a POSIX-compatible way or a way, which works on all shells, to get the version number of the shell, which is running?

With $SHELL ps -p$$ -ocmd= you can get the name/binary, so $SHELL --version $(ps -p$$ -ocmd=) --version may work, but in a very simple sh shell (tested in Travis-CI/Ubuntu Trusty) this does not work neither.

Is there a way or maybe at least a short/easy way, which works for most shells (e.g. in case you have to use case to differentiate between the shells)?

Edit: This is only for printing the version of the shell that is used, i.e. just for informative purposes. I do not want to do something with the return value, just show it to the user.

rugk
  • 3,086

4 Answers4

7

There is no --version flag defined in the POSIX standard for the sh utility. There is also no standard environment variable or shell variable that contain any kind of version information. This means that the shell doesn't even need make its version available at all.

If you need to test for a particular version of a shell, then your script is not likely to be POSIX conformant, so the question (as it stands) becomes irrelevant.

For the ksh shell, see How can I safely get the version of ksh?

Also related: How to test what shell I am using in a terminal?

Kusalananda
  • 333,661
  • "If you need to test for a particular version of a shell" I don't need it, it is just debugging output. – rugk Sep 18 '17 at 15:56
  • 2
    @rugk debugging what? If you're running a shell script, it will only run on a small subset of shells anyway: those that use whatever shell syntax you're using. You might get more helpful answers if you explain what you are trying to do more clearly and what shells you will be looking at. – terdon Sep 18 '17 at 16:33
  • If you make it POSIX-compatible, it won't. Then it should be able to run on any shell… – rugk Sep 18 '17 at 20:59
  • 2
    The output of getconf POSIX2_VERSION seems to be the only think that is relevant here: the version of the Shell and Utilities volume of POSIX.1 to which the implementation conforms (or at least intends to) in the current environment. – Stéphane Chazelas Sep 18 '17 at 21:32
  • @rugk no, only on POSIX compatible shells. Many, many shells are not POSIX shells and use completely different syntax. All of the csh family, for instance. – terdon Sep 20 '17 at 12:10
3

Unfortunately, the current edition of POSIX sh standards does not offer either an official command line flag nor environment variable for reliably accessing the shell's version.

Fortunately, there are workarounds. One can try supplying --version to the shell, for most bash derivatives (except dash and posh). Or echo "$BASH_VERSION" (bash), echo "$ZSH_VERSION" (zsh), echo "${.sh.version}" (ksh).

Some packages differentiate versions in the binary filename, like how Python v3's command line application name is python3. It's possible that the current process name of a shell includes the major version, or a shell's binary path like /bin/sh links to a more specific path like /bin/bash4.4.12. Unfortunately, this convention has not made its way into the tiny community of shell developers, so those checks are unlikely to yield useful results.

Yet, the packaging system may present a version number for the shell package in question. So run dpkg -l <shell> (Debian derivatives), yum -l <shell> (RHEL derivatives), emerge -Opv <shell> (Gentoo), pacman -Qi dash (Arch), brew list dash (Homebrew), and so on. If the shell in question is sh, then that shell is likely provided by the coreutils package, so query the package manager for coreutils rather than sh.

For RVM kin, cygwin, and other unix-like environments, the directory containing the shell may name the version of the shell. So grab the absolute path to the shell application and see if the name appears there.

Finally, the shell may simply be provided by the operating system, so uname -a; cat /etc/*release* may provide at least some kind of identifier for tracking the shell version.

If all of these commands are joined with semicolons in a script, one would have a reasonably comprehensive brute force, nmap-like tool for identifying a given shell's version.

mcandre
  • 384
2

I actually found a way. The shell testing unit shunit2, itself written as shell scripts, has a "version library", which also has code to detect the used shell version:

VERSIONS_SHELLS="ash /bin/bash /bin/dash /bin/ksh /bin/pdksh /bin/sh /bin/zsh"

versions_shellVersion() {
  shell_=$1

  shell_present_=${FALSE}
  case "${shell_}" in
    ash)
      [ -x '/bin/busybox' ] && shell_present_=${TRUE}
      ;;
    *)
      [ -x "${shell_}" ] && shell_present_=${TRUE}
      ;;
  esac
  if [ ${shell_present_} -eq ${FALSE} ]; then
    echo 'not installed'
    return ${FALSE}
  fi

  version_=''
  case ${shell_} in
    */sh)
      # TODO(kward): fix this
      ## this could be one of any number of shells. try until one fits.
      #version_=`versions_shell_bash ${shell_}`
      ## dash cannot be self determined yet
      #[ -z "${version_}" ] && version_=`versions_shell_ksh ${shell_}`
      ## pdksh is covered in versions_shell_ksh()
      #[ -z "${version_}" ] && version_=`versions_shell_zsh ${shell_}`
      ;;
    ash) version_=`versions_shell_ash ${shell_}` ;;
    */bash) version_=`versions_shell_bash ${shell_}` ;;
    */dash)
      # simply assuming Ubuntu Linux until somebody comes up with a better
      # test. the following test will return an empty string if dash is not
      # installed.
      version_=`versions_shell_dash`
      ;;
    */ksh) version_=`versions_shell_ksh ${shell_}` ;;
    */pdksh) version_=`versions_shell_pdksh ${shell_}` ;;
    */zsh) version_=`versions_shell_zsh ${shell_}` ;;
    *) version_='invalid'
  esac

  echo ${version_:-unknown}
  unset shell_ version_
}

# The ash shell is included in BusyBox.
versions_shell_ash() {
  busybox --help |head -1 |sed 's/BusyBox v\([0-9.]*\) .*/\1/'
}

versions_shell_bash() {
  $1 --version 2>&1 |grep 'GNU bash' |sed 's/.*version \([^ ]*\).*/\1/'
}

versions_shell_dash() {
  eval dpkg >/dev/null 2>&1
  [ $? -eq 127 ] && return  # return if dpkg not found

  dpkg -l |grep ' dash ' |awk '{print $3}'
}

versions_shell_ksh() {
  versions_shell_=$1

  # try a few different ways to figure out the version
  versions_version_=`${versions_shell_} --version : 2>&1`
  if [ $? -eq 0 ]; then
    versions_version_=`echo "${versions_version_}" \
      |sed 's/.*\([0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]\).*/\1/'`
  else
    versions_version_=''
  fi

  if [ -z "${versions_version_}" ]; then
    _versions_have_strings
    versions_version_=`strings ${versions_shell_} 2>&1 \
      |grep Version \
      |sed 's/^.*Version \(.*\)$/\1/;s/ s+ \$$//;s/ /-/g'`
  fi

  if [ -z "${versions_version_}" ]; then
    versions_version_=`versions_shell_pdksh ${versions_shell_}`
  fi

  echo ${versions_version_}
  unset versions_shell_ versions_version_
}

versions_shell_pdksh() {
  _versions_have_strings
  strings $1 2>&1 \
  |grep 'PD KSH' \
  |sed -e 's/.*PD KSH \(.*\)/\1/;s/ /-/g'
}

versions_shell_zsh() {
  versions_shell_=$1

  # try a few different ways to figure out the version
  versions_version_=`echo 'echo ${ZSH_VERSION}' |${versions_shell_}`

  if [ -z "${versions_version_}" ]; then
    versions_version_=`${versions_shell_} --version 2>&1 |awk '{print $2}'`
  fi

  echo ${versions_version_}
  unset versions_shell_ versions_version_
}

That might even work to get out the version of a shell, when it is not used, but I admit that in my use case just using $BASH_VERSION or similar, may be more effective.

Licensed under the Apache License by @kward.

rugk
  • 3,086
1

As terdon said:

If you're running a shell script, it will only run on a small subset of shells anyway: those that use whatever shell syntax you're using. – terdon♦ 5 hours ago

To address your reply:

If you make it POSIX-compatible, it won't. Then it should be able to run on any shell…

The point is that there is no POSIX-specified way to get the version of the shell. So if you have a check in your shell script for version, it is already not strictly POSIX.

Keep in mind that POSIX is a set of specifications.

If what you really want is version-related debugging, you will likely have to have a separate method for each shell implementation that you are targeting, with a bunch of conditional checks and heuristics. But it won't be guaranteed to work on future shells, even if those shells comply fully with the POSIX specifications.

The best you can do is heuristics. I suggest you start by listing out the shell implementations you are interested in supporting for your version checks, and then inspect the man page for each to see how to get that shell's version. Lots and lots of testing will be required; don't forget to check old versions of the shell implementations of interest, if you really want to support ANY shell with an sh-like syntax.


Depending on what sort of software you are developing, you might be better off to just add "bash version 3+" to your dependency list and have done with it.

Wildcard
  • 36,499