关于os检测:如何从Bash脚本中检测操作系统?

How to detect the OS from a Bash script?

我想将.bashrc.bash_login文件保存在版本控制中,以便我可以在我使用的所有计算机之间使用它们。 问题是我有一些操作系统特定的别名,所以我一直在寻找一种方法来确定脚本是否在Mac  OS  X,Linux或Cygwin上运行。

在Bash脚本中检测操作系统的正确方法是什么?


我认为以下内容应该有效。我不确定win32

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if [["$OSTYPE" =="linux-gnu" ]]; then
        # ...
elif [["$OSTYPE" =="darwin"* ]]; then
        # Mac OSX
elif [["$OSTYPE" =="cygwin" ]]; then
        # POSIX compatibility layer and Linux environment emulation for Windows
elif [["$OSTYPE" =="msys" ]]; then
        # Lightweight shell and GNU utilities compiled for Windows (part of MinGW)
elif [["$OSTYPE" =="win32" ]]; then
        # I'm not sure this can happen.
elif [["$OSTYPE" =="freebsd"* ]]; then
        # ...
else
        # Unknown.
fi


对于我的.bashrc,我使用以下代码:

1
2
3
4
5
6
7
platform='unknown'
unamestr=`uname`
if [["$unamestr" == 'Linux' ]]; then
   platform='linux'
elif [["$unamestr" == 'FreeBSD' ]]; then
   platform='freebsd'
fi

然后我做的事情如下:

1
2
3
4
5
if [[ $platform == 'linux' ]]; then
   alias ls='ls --color=auto'
elif [[ $platform == 'freebsd' ]]; then
   alias ls='ls -G'
fi

这很难看,但它确实有效。如果您愿意,可以使用case而不是if


bash手册页说变量OSTYPE存储操作系统的名称:

OSTYPE Automatically set to a string that describes the operating system on which bash is executing. The default is system-
dependent.

它在这里设置为linux-gnu


$OSTYPE

您可以简单地使用预定义的$OSTYPE变量,例如:

1
2
3
4
5
6
7
8
case"$OSTYPE" in
  solaris*) echo"SOLARIS" ;;
  darwin*)  echo"OSX" ;;
  linux*)   echo"LINUX" ;;
  bsd*)     echo"BSD" ;;
  msys*)    echo"WINDOWS" ;;
  *)        echo"unknown: $OSTYPE" ;;
esac

但是旧版的shell(例如Bourne shell)无法识别它。

uname

另一种方法是基于uname命令检测平台。

请参阅以下脚本(准备包含在.bashrc中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Detect the platform (similar to $OSTYPE)
OS="`uname`"
case $OS in
  'Linux')
    OS='Linux'
    alias ls='ls --color=auto'
    ;;
  'FreeBSD')
    OS='FreeBSD'
    alias ls='ls -G'
    ;;
  'WindowsNT')
    OS='Windows'
    ;;
  'Darwin')
    OS='Mac'
    ;;
  'SunOS')
    OS='Solaris'
    ;;
  'AIX') ;;
  *) ;;
esac

你可以在我的.bashrc中找到一些实际的例子。

以下是Travis CI上使用的类似版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
case $(uname | tr '[:upper:]' '[:lower:]') in
  linux*)
    export TRAVIS_OS_NAME=linux
    ;;
  darwin*)
    export TRAVIS_OS_NAME=osx
    ;;
  msys*)
    export TRAVIS_OS_NAME=windows
    ;;
  *)
    export TRAVIS_OS_NAME=notset
    ;;
esac


检测操作系统和CPU类型并不容易移植。我有一个大约100行的sh脚本,适用于各种各样的Unix平台:自1988年以来我使用过的任何系统。

关键要素是

  • uname -p是处理器类型,但在现代Unix平台上通常是unknown

  • uname -m将在某些Unix系统上提供"机器硬件名称"。

  • /bin/arch,如果存在,通常会给出处理器的类型。

  • 没有参数的uname将命名操作系统。

最终,您将不得不考虑平台之间的区别以及您想要制作它们的精确程度。例如,为了简单起见,我将i386i686,任何"Pentium*"和任何"AMD*Athlon*"都视为x86

我的~/.profile在启动时运行一个脚本,它将一个变量设置为一个字符串,表示CPU和操作系统的组合。我有基于此的平台特定的binmanlibinclude目录。然后我设置了一大堆环境变量。因此,例如,用于重新格式化邮件的shell脚本可以调用例如$LIB/mailfmt,其是特定于平台的可执行二进制文件。

如果你想偷工减料,uname -m和plain uname会告诉你在许多平台上你想知道什么。在需要时添加其他东西。 (并使用case,而不是嵌套if!)


我建议使用这个完整的bash代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
lowercase(){
    echo"$1" | sed"y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/"
}

OS=`lowercase \`uname\``
KERNEL=`uname -r`
MACH=`uname -m`

if ["{$OS}" =="windowsnt" ]; then
    OS=windows
elif ["{$OS}" =="darwin" ]; then
    OS=mac
else
    OS=`uname`
    if ["${OS}" ="SunOS" ] ; then
        OS=Solaris
        ARCH=`uname -p`
        OSSTR="${OS} ${REV}(${ARCH} `uname -v`)"
    elif ["${OS}" ="AIX" ] ; then
        OSSTR="${OS} `oslevel` (`oslevel -r`)"
    elif ["${OS}" ="Linux" ] ; then
        if [ -f /etc/redhat-release ] ; then
            DistroBasedOn='RedHat'
            DIST=`cat /etc/redhat-release |sed s/\ release.*//`
            PSUEDONAME=`cat /etc/redhat-release | sed s/.*\(// | sed s/\)//`
            REV=`cat /etc/redhat-release | sed s/.*release\ // | sed s/\ .*//`
        elif [ -f /etc/SuSE-release ] ; then
            DistroBasedOn='SuSe'
            PSUEDONAME=`cat /etc/SuSE-release | tr"
"
' '| sed s/VERSION.*//`
            REV=`cat /etc/SuSE-release | tr"
"
' ' | sed s/.*=\ //`
        elif [ -f /etc/mandrake-release ] ; then
            DistroBasedOn='Mandrake'
            PSUEDONAME=`cat /etc/mandrake-release | sed s/.*\(// | sed s/\)//`
            REV=`cat /etc/mandrake-release | sed s/.*release\ // | sed s/\ .*//`
        elif [ -f /etc/debian_version ] ; then
            DistroBasedOn='Debian'
            DIST=`cat /etc/lsb-release | grep '^DISTRIB_ID' | awk -F=  '{ print $2 }'`
            PSUEDONAME=`cat /etc/lsb-release | grep '^DISTRIB_CODENAME' | awk -F=  '{ print $2 }'`
            REV=`cat /etc/lsb-release | grep '^DISTRIB_RELEASE' | awk -F=  '{ print $2 }'`
        fi
        if [ -f /etc/UnitedLinux-release ] ; then
            DIST="${DIST}[`cat /etc/UnitedLinux-release | tr"
" ' ' | sed s/VERSION.*//`
]"

        fi
        OS=`lowercase $OS`
        DistroBasedOn=`lowercase $DistroBasedOn`
        readonly OS
        readonly DIST
        readonly DistroBasedOn
        readonly PSUEDONAME
        readonly REV
        readonly KERNEL
        readonly MACH
    fi

fi

这里有更多示例:https://github.com/coto/server-easy-install/blob/master/lib/core.sh


在bash中,使用$OSTYPE$HOSTTYPE,如文档所述;这就是我做的。如果这还不够,如果连unameuname -a(或其他适当的选项)都没有提供足够的信息,那么总会有GNU项目中的config.guess脚本完全用于此目的。


尝试使用"uname"。例如,在Linux中:"uname -a"。

根据手册页,uname符合SVr4和POSIX,因此它应该可以在Mac  OS  X和Cygwin上使用,但我无法确认。

BTW:$ OSTYPE也设置为linux-gnu :)


我建议避免一些这些答案。不要忘记您可以选择其他形式的字符串比较,这将清除大多数变体或提供的丑陋代码。

一个这样的解决方案是简单的检查,例如:


if [["$OSTYPE" =~ ^darwin ]]; then

除了版本后缀之外,它还具有匹配任何版本的Darwin的额外好处。这也适用于人们可能期望的Linux的任何变体。

您可以在我的dotfiles中看到一些其他示例


我在.bashrc中写了这些糖:

1
2
3
4
5
6
7
8
9
if_os () { [[ $OSTYPE == *$1* ]]; }
if_nix () {
    case"$OSTYPE" in
        *linux*|*hurd*|*msys*|*cygwin*|*sua*|*interix*) sys="gnu";;
        *bsd*|*darwin*) sys="bsd";;
        *sunos*|*solaris*|*indiana*|*illumos*|*smartos*) sys="sun";;
    esac
    [["${sys}" =="$1" ]];
}

所以我可以这样做:

1
2
3
4
5
if_nix gnu && alias ls='ls --color=auto' && export LS_COLORS="..."
if_nix bsd && export CLICOLORS=on && export LSCOLORS="..."
if_os linux && alias psg="ps -FA | grep" #alternative to pgrep
if_nix bsd && alias psg="ps -alwx | grep -i" #alternative to pgrep
if_os darwin && alias finder="open -R"


我编写了一个个人Bash库和脚本框架,它使用GNU shtool进行相当准确的平台检测。

GNU shtool是一组非常便携的脚本,除了其他有用的东西之外,还包含'shtool platform'命令。这是输出:

1
shtool platform -v -F"%sc (%ac) %st (%at) %sp (%ap)"

在几台不同的机器上:

1
2
3
4
5
6
7
8
Mac OS X Leopard:
    4.4BSD/Mach3.0 (iX86) Apple Darwin 9.6.0 (i386) Apple Mac OS X 10.5.6 (iX86)

Ubuntu Jaunty server:
    LSB (iX86) GNU/Linux 2.9/2.6 (i686) Ubuntu 9.04 (iX86)

Debian Lenny:
    LSB (iX86) GNU/Linux 2.7/2.6 (i686) Debian GNU/Linux 5.0 (iX86)

如您所见,这会产生非常令人满意的结果。 GNU shtool有点慢,所以我实际上将平台标识存储并更新到我的脚本调用的系统上的文件中。这是我的框架,所以这对我有用,但你的里程可能会有所不同。

现在,您必须找到一种方法来使用脚本打包shtool,但这并不是一项艰苦的练习。你也总是可以依靠uname输出。

编辑:

我错过了Teddy关于config.guess的帖子(某种程度上)。这些是非常相似的脚本,但不一样。我个人也将shtool用于其他用途,它对我来说一直很好用。


您可以使用以下内容:

1
OS=$(uname -s)

然后您可以在脚本中使用OS变量。


1
uname

要么

1
uname -a

如果你想了解更多信息


这应该可以安全地用于所有发行版。

1
$ cat /etc/*release

这产生了这样的东西。

1
2
3
4
5
6
7
8
9
10
11
12
13
     DISTRIB_ID=LinuxMint
     DISTRIB_RELEASE=17
     DISTRIB_CODENAME=qiana
     DISTRIB_DESCRIPTION="Linux Mint 17 Qiana"
     NAME="Ubuntu"
     VERSION="14.04.1 LTS, Trusty Tahr"
     ID=ubuntu
     ID_LIKE=debian
     PRETTY_NAME="Ubuntu 14.04.1 LTS"
     VERSION_ID="14.04"
     HOME_URL="http://www.ubuntu.com/"
     SUPPORT_URL="http://help.ubuntu.com/"
     BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"

根据需要提取/分配变量

注意:在某些设置上。这也可能会给您一些您可以忽略的错误。

1
     cat: /etc/upstream-release: Is a directory


您可以使用以下if子句并根据需要展开它:

1
2
3
4
5
6
7
if ["${OSTYPE//[0-9.]/}" =="darwin" ]
then
    aminute_ago="-v-1M"
elif  ["${OSTYPE//[0-9.]/}" =="linux-gnu" ]
then
    aminute_ago="-d "1 minute ago""
fi

下面是一种检测基于Debian和RedHat的Linux操作系统的方法,它使用/ etc / lsb-release和/ etc / os-release(取决于你正在使用的Linux风格)并采取基于它的简单操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/bin/bash
set -e

YUM_PACKAGE_NAME="python python-devl python-pip openssl-devel"
DEB_PACKAGE_NAME="python2.7 python-dev python-pip libssl-dev"

 if cat /etc/*release | grep ^NAME | grep CentOS; then
    echo"==============================================="
    echo"Installing packages $YUM_PACKAGE_NAME on CentOS"
    echo"==============================================="
    yum install -y $YUM_PACKAGE_NAME
 elif cat /etc/*release | grep ^NAME | grep Red; then
    echo"==============================================="
    echo"Installing packages $YUM_PACKAGE_NAME on RedHat"
    echo"==============================================="
    yum install -y $YUM_PACKAGE_NAME
 elif cat /etc/*release | grep ^NAME | grep Fedora; then
    echo"================================================"
    echo"Installing packages $YUM_PACKAGE_NAME on Fedorea"
    echo"================================================"
    yum install -y $YUM_PACKAGE_NAME
 elif cat /etc/*release | grep ^NAME | grep Ubuntu; then
    echo"==============================================="
    echo"Installing packages $DEB_PACKAGE_NAME on Ubuntu"
    echo"==============================================="
    apt-get update
    apt-get install -y $DEB_PACKAGE_NAME
 elif cat /etc/*release | grep ^NAME | grep Debian ; then
    echo"==============================================="
    echo"Installing packages $DEB_PACKAGE_NAME on Debian"
    echo"==============================================="
    apt-get update
    apt-get install -y $DEB_PACKAGE_NAME
 elif cat /etc/*release | grep ^NAME | grep Mint ; then
    echo"============================================="
    echo"Installing packages $DEB_PACKAGE_NAME on Mint"
    echo"============================================="
    apt-get update
    apt-get install -y $DEB_PACKAGE_NAME
 elif cat /etc/*release | grep ^NAME | grep Knoppix ; then
    echo"================================================="
    echo"Installing packages $DEB_PACKAGE_NAME on Kanoppix"
    echo"================================================="
    apt-get update
    apt-get install -y $DEB_PACKAGE_NAME
 else
    echo"OS NOT DETECTED, couldn't install package $PACKAGE"
    exit 1;
 fi

exit 0

Ubuntu Linux的输出示例:

1
2
3
4
5
6
7
8
9
10
delivery@delivery-E5450$ sudo sh detect_os.sh
[sudo] password for delivery:
NAME="Ubuntu"
===============================================
Installing packages python2.7 python-dev python-pip libssl-dev on Ubuntu
===============================================
Ign http://dl.google.com stable InRelease
Get:1 http://dl.google.com stable Release.gpg [916 B]                          
Get:2 http://dl.google.com stable Release [1.189 B]
...

我倾向于将.bashrc和.bash_alias保存在所有平台都可以访问的文件共享上。这就是我在.bash_alias中克服问题的方法:

1
2
3
if [[ -f (name of share)/.bash_alias_$(uname) ]]; then
    . (name of share)/.bash_alias_$(uname)
fi

我有一个.bash_alias_Linux,例如:

1
alias ls='ls --color=auto'

这样我将平台特定和可移植代码分开,你可以为.bashrc做同样的事情


试试这个:

1
2
DISTRO=$(cat /etc/*-release | grep -w NAME | cut -d= -f2 | tr -d '"')
echo"Determined platform: $DISTRO"


我在几个Linux发行版上尝试了上述消息,发现以下内容最适合我。这是一个简短,简洁的单词答案,适用于Windows上的Bash。

1
OS=$(cat /etc/*release | grep ^NAME | tr -d 'NAME="') #$ echo $OS # Ubuntu


如果linux发行版是Debian或Ubunu,它会检查一堆known文件以识别,然后它默认为$OSTYPE变量。

1
2
3
4
5
os='Uknown'
unamestr="${OSTYPE//[0-9.]/}"
os=$( compgen -G"/etc/*release"> /dev/null  && cat /etc/*release | grep ^NAME | tr -d 'NAME="'  ||  echo"$unamestr")

echo"$os"

执行以下操作有助于为ubuntu正确执行检查:

1
2
3
if [["$OSTYPE" =~ ^linux ]]; then
    sudo apt-get install <some-package>
fi