POSIX兼容/跨shell方式获取正在运行的shell版本?

POSIX兼容/跨shell方式获取正在运行的shell版本?

是否有一种 POSIX 兼容的方法或一种适用于所有 shell 的方法来获取正在运行的 shell 的版本号?

您可以获取名称/二进制文件,因此可能可以工作,但在非常简单的shell 中(在 Travis-CI/Ubuntu Trusty 中测试),这也不起作用。$SHELL ps -p$$ -ocmd=$SHELL --version $(ps -p$$ -ocmd=) --versionsh

有没有一种方法,或者至少是一种简短/简单的方法,适用于大多数 shell(例如,如果您必须用来case区分 shell)?

编辑:这仅用于打印所使用的 shell 的版本,即仅用于提供信息的目的。我愿意不是想要对返回值做一些事情,只需将其显示给用户即可。

答案1

没有--version定义标志sh实用程序的 POSIX 标准。也没有包含任何类型版本信息的标准环境变量或 shell 变量。这意味着外壳甚至不需要使其版本可用。

如果您需要测试特定版本的 shell,那么您的脚本不太可能符合 POSIX,因此问题(就目前情况而言)变得无关紧要。

对于ksh外壳,请参见如何安全地获取 ksh 版本?

还相关:如何测试我在终端中使用的 shell?

答案2

不幸的是,当前版本的 POSIX sh 标准既不提供官方命令行标志,也不提供用于可靠访问 shell 版本的环境变量。

幸运的是,有解决方法。对于大多数 bash 衍生品(dash 和 posh 除外),可以尝试--version向 shell 提供。或者echo "$BASH_VERSION"(bash)、echo "$ZSH_VERSION"(zsh)、echo "${.sh.version}"(ksh)。

有些包在二进制文件名中区分版本,例如 Python v3 的命令行应用程序名称是python3. shell 的当前进程名称可能包含主要版本,或者 shell 的二进制路径(例如/bin/sh指向更具体路径(例如/bin/bash4.4.12.不幸的是,这个约定还没有进入 shell 开发人员的小社区,因此这些检查不太可能产生有用的结果。

然而,打包系统可能会提供相关 shell 包的版本号。所以运行dpkg -l <shell>(Debian 衍生品)、yum -l <shell>(RHEL 衍生品)、emerge -Opv <shell>(Gentoo)、pacman -Qi dash(Arch)、brew list dash(Homebrew)等。如果有问题的 shell 是sh,那么该 shell 可能是由 coreutils 包提供的,因此请查询包管理器而coreutils不是sh

对于 RVM kin、cygwin 和其他类 UNIX 环境,包含 shell 的目录可能会命名 shell 的版本。因此,获取 shell 应用程序的绝对路径并查看名称是否出现在那里。

最后,外壳可以简单地由操作系统提供,因此uname -a; cat /etc/*release*可以提供至少某种用于跟踪外壳版本的标识符。

如果所有这些命令在脚本中都用分号连接起来,那么人们将拥有一个相当全面的、类似于 nmap 的强力工具来识别给定 shell 的版本。

答案3

我其实找到了办法。外壳测试单元舒尼特2,本身编写为 shell 脚本,有一个“版本库”,其中还有检测所使用的 shell 版本的代码:

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_
}

当不使用 shell 时,这甚至可能可以获取 shell 的版本,但我承认在我的用例中,仅使用$BASH_VERSION或类似的方法可能会更有效。

已获得许可阿帕奇许可证由@kward。

答案4

正如特登所说:

如果您正在运行 shell 脚本,那么它只会在一小部分 shell 上运行:那些使用您正在使用的任何 shell 语法的 shell。 – terdon 5 小时前

针对您的回复:

如果你让它兼容 POSIX,它就不会。那么它应该能够在任何 shell 上运行......

重点是有没有 POSIX 指定的方式来获取 shell 的版本。因此,如果您检查 shell 脚本的版本,它已经不是严格的 POSIX。

请记住 POSIX 是一组规格。

如果您真正想要的是与版本相关的调试,则可能必须为每个 shell 提供单独的方法执行 您所瞄准的,带有一堆条件检查和启发式。但不能保证它适用于未来的 shell,即使这些 shell 完全符合 POSIX 规范。

你能做的最好的事情就是启发法。我建议你首先列出 shell实施您有兴趣支持版本检查,然后检查每个版本的手册页以了解如何获取该 shell 的版本。需要进行大量的测试;如果您确实想使用sh类似语法支持任何 shell,请不要忘记检查感兴趣的 shell 实现的旧版本。


根据您正在开发的软件类型,您可能最好将“bash version 3+”添加到您的依赖项列表中并完成它。

相关内容