如何从Bash脚本中检测操作系统?

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

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

424393 次浏览

bashmanpage表示变量OSTYPE存储操作系统的名称:

OSTYPE自动设置为描述bash正在其上执行的操作系统的字符串。默认值是system- 依赖。

这里设置为linux-gnu

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

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

顺便说一句:$OSTYPE也设置为linux-gnu:)

uname

uname -a

如果你想了解更多信息

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

platform='unknown'
unamestr=$(uname)
if [[ "$unamestr" == 'Linux' ]]; then
platform='linux'
elif [[ "$unamestr" == 'FreeBSD' ]]; then
platform='freebsd'
fi

然后我做一些事情,比如:

if [[ $platform == 'linux' ]]; then
alias ls='ls --color=auto'
elif [[ $platform == 'freebsd' ]]; then
alias ls='ls -G'
fi

它很丑,但它有效。如果您愿意,可以使用case而不是if

检测操作系统和CPU类型并不那么容易便携。我有一个大约100行的sh脚本,可以在各种Unix平台上运行:我自1988年以来使用的任何系统。

关键要素是

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

  • uname -m将在某些Unix系统上给出“机器硬件名称”。

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

  • #不带参数的EYZ0将命名操作系统。

最终,您将不得不考虑平台和你想让他们有多好。之间的区别例如,为了简单起见,我将i386i686,任何“Pentium*”和任何“AMD*Athlon*”都视为x86

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

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

在bash中,使用$OSTYPE$HOSTTYPE,如文档所示;这就是我所做的。如果这还不够,并且即使unameuname -a(或其他适当的选项)也没有提供足够的信息,那么总会有来自GNU项目的config.guess脚本,正是为此目的而制作的。

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

GNU shool是一组非常可移植的脚本,其中包含“shool平台”命令。这是以下输出:

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

在不同的机器上:

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 shool有点慢,所以我实际上将平台标识存储和更新在我的脚本调用的系统上的文件中。这是我的框架,所以对我有用,但因人而异。

现在,您必须找到一种方法来将shool与您的脚本一起打包,但这并不是一项艰巨的工作。您也可以随时依靠uname输出。

编辑:

我错过了Teddy关于config.guess的帖子(不知何故)。这些脚本非常相似,但不一样。我个人也使用shool进行其他用途,它对我来说效果很好。

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

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 "\n" ' '| sed s/VERSION.*//`
REV=`cat /etc/SuSE-release | tr "\n" ' ' | 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 "\n" ' ' | 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
echo $OS
echo $KERNEL
echo $MACH

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

我认为下面应该工作。但我不确定win32

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

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

一种解决方案是简单的检查,例如:

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

它有一个额外的好处,可以匹配任何版本的Darwin,尽管它有版本后缀。这也适用于人们可能期望的Linux的任何变体。

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

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

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

$OSTYPE

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

case "$OSTYPE" in
solaris*) echo "SOLARIS" ;;
darwin*)  echo "OSX" ;;
linux*)   echo "LINUX" ;;
bsd*)     echo "BSD" ;;
msys*)    echo "WINDOWS" ;;
cygwin*)  echo "ALSO WINDOWS" ;;
*)        echo "unknown: $OSTYPE" ;;
esac

但是它是不被旧贝壳识别(例如Bourne shell)。


uname

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

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

# 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上使用的类似版本:

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

在所有发行版上使用应该是安全的。

$ cat /etc/*release

这就产生了这样的东西。

     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/"

根据需要提取/分配变量

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

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

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

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

例如,我有一个bash_alias_Linux:

alias ls='ls --color=auto'

通过这种方式,我将特定于平台的代码和可移植代码分开,您可以对. bashrc执行相同的操作

我在我的.bashrc中写了这些糖:

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" ]];
}

所以我可以做这样的事情:

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"

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

if [ "${OSTYPE//[0-9.]/}" == "darwin" ]
then
aminute_ago="-v-1M"
elif  [ "${OSTYPE//[0-9.]/}" == "linux-gnu" ]
then
aminute_ago="-d \"1 minute ago\""
fi

下面是使用/etc/ls b-释放/etc/os-释放(取决于您使用的Linux风格)检测基于Debian和RedHat的LinuxOS的方法,并根据它采取简单的操作。

#!/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

输出示例 forUbuntuLinux:

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]
...

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

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

您可以使用以下内容:

OS=$(uname -s)

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

这会检查一堆known文件以确定linux发行版是Debian还是Ubunu,然后默认为$OSTYPE变量。

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


echo "$os"

试试这个:

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

如果有人对检测WSL与WSL版本2感兴趣,这就是我使用的。

#!/usr/bin/env bash


unameOut=$(uname -a)
case "${unameOut}" in
*Microsoft*)     OS="WSL";; #must be first since Windows subsystem for linux will have Linux in the name too
*microsoft*)     OS="WSL2";; #WARNING: My v2 uses ubuntu 20.4 at the moment slightly different name may not always work
Linux*)     OS="Linux";;
Darwin*)    OS="Mac";;
CYGWIN*)    OS="Cygwin";;
MINGW*)     OS="Windows";;
*Msys)     OS="Windows";;
*)          OS="UNKNOWN:${unameOut}"
esac


echo ${OS};