如何检查当前运行环境是Cygwin、Mac还是Linux?

462

我有一个在Windows/Cygwin、Mac和Linux上都使用的shell脚本。每个版本都需要稍微不同的变量。

如何让shell/bash脚本检测它是在Cygwin、Mac还是Linux上运行?

10个回答

467
通常,uname 和它的各种选项可以告诉你你正在运行的环境:
pax> uname -a
CYGWIN_NT-5.1 IBM-L3F3936 1.5.25(0.156/4/2) 2008-06-12 19:34 i686 Cygwin

pax> uname -s
CYGWIN_NT-5.1

根据评论区中非常有帮助的schot的建议,uname -s在OSX上返回Darwin,在Linux上返回Linux,而我的Cygwin则返回CYGWIN_NT-5.1。但是您可能需要尝试各种不同的版本。

因此,进行此类检查的bash代码将如下所示:

unameOut="$(uname -s)"
case "${unameOut}" in
    Linux*)     machine=Linux;;
    Darwin*)    machine=Mac;;
    CYGWIN*)    machine=Cygwin;;
    MINGW*)     machine=MinGw;;
    MSYS_NT*)   machine=Git;;
    *)          machine="UNKNOWN:${unameOut}"
esac
echo ${machine}

请注意,我在这里假设您实际上是在CygWin(即其bash shell)中运行,因此路径应该已经正确设置。正如一位评论者所指出的那样,您可以从cmd本身运行bash程序并传递脚本,但这可能导致路径未按需要设置。
如果您正在这样做,那么您有责任确保调用了正确的可执行文件(即CygWin的可执行文件),可能需要事先修改路径或完全指定可执行文件位置(例如/c/cygwin/bin/uname)。

14
有时候少就是多;顺便提一下,维基百科上有一个uname输出示例的表格,网址是http://en.wikipedia.org/wiki/Uname。 - schot
1
在Windows 7上,Git Bash的uname -s输出为“MINGW32_NT-6.1”。此外,没有“/cygdrive”前缀,只有“/c”代表“C:”。 - ColinM
9
Git Bash不是Cygwin,它是MinGW(微型GNU for MS Windows),这就是为什么它的行为与Cygwin不同的原因。 - Boyd Stephen Smith Jr.
我推荐另一个答案,因为这个答案没有涉及到 OP 部分的问题“如何让 shell/bash 脚本检测...”,而另一个答案有。 - Jesse Chisholm
1
你可以在Windows 10中运行Bash而无需cygwin。uname将返回类似于:MSYS_NT-10.0-19041,因此匹配MSYS_NT *)即可解决问题。 - DoomGoober
显示剩余2条评论

393

检测三种不同的操作系统类型(GNU / Linux、Mac OS X、Windows NT)

注释

  • 在您的bash脚本中,使用#!/usr/bin/env bash而不是#!/bin/sh,以防止/bin/sh在不同平台上链接到不同的默认shell引起问题。否则可能会出现错误,例如“意外运算符”,这就是我电脑(Ubuntu 64位12.04)上发生的情况。
  • Mac OS X 10.6.8(Snow Leopard)没有expr程序,除非您安装了它,因此我只使用uname

设计

  1. 使用uname获取系统信息(-s参数)。
  2. 使用exprsubstr处理字符串。
  3. 使用if elif fi进行匹配。
  4. 如果需要,可以添加更多的系统支持,只需遵循uname -s规范。

实现

#!/usr/bin/env bash

if [ "$(uname)" == "Darwin" ]; then
    # Do something under Mac OS X platform        
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then
    # Do something under GNU/Linux platform
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then
    # Do something under 32 bits Windows NT platform
elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then
    # Do something under 64 bits Windows NT platform
fi

测试

  • Linux(Ubuntu 12.04 LTS,Kernel 3.2.0)测试通过。
  • OS X(10.6.8 Snow Leopard)测试通过。
  • Windows(Windows 7 64位)测试通过。

我学到了什么

  1. 检查开闭引号。
  2. 检查括号和大括号 {} 是否缺失。

参考资料


对于MinGW,检查以下内容可能更有意义:[ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ] - Achal Dave
2
@Albert:表达式"$(expr substr $(uname -s) 1 5)"有点奇怪。有更好的方法来实现,例如:if [ \uname -s` == CYGWIN* ]; then。意思是:如果uname -s`以CYGWIN开头,则... - David Ferenczy Rogožan
12
@DawidFerenczy 我认为需要使用双括号,就像这样 if [[ $(uname -s) == CYGWIN* ]]; then - rogerdpack
1
这个并没有检测到Cygwin,正如问题中所问的那样。 - rmcclellan
3
为什么这个检查只针对 Linux 的前 5 个字符?现代的 Linux 发行版中是否有 uname -s 返回值不是 "Linux" 的例子? - ktbiz
显示剩余3条评论

191

使用uname -s--kernel-name)是因为一些操作系统不支持uname -o--operating-system),例如Mac OSSolaris。您还可以只使用uname而不带任何参数,因为默认参数是-s--kernel-name)。

为了区分WSL和Linux,einarmagnus建议使用uname -sr--kernel-name --kernel-release),如下面的脚本所示。

#!/bin/sh

case "$(uname -sr)" in

   Darwin*)
     echo 'Mac OS X'
     ;;

   Linux*Microsoft*)
     echo 'WSL'  # Windows Subsystem for Linux
     ;;

   Linux*)
     echo 'Linux'
     ;;

   CYGWIN*|MINGW*|MINGW32*|MSYS*)
     echo 'MS Windows'
     ;;

   # Add here more strings to compare
   # See correspondence table at the bottom of this answer

   *)
     echo 'Other OS' 
     ;;
esac

下面的Makefile受到Git项目(config.mak.uname)的启发。
ifdef MSVC     # Avoid the MingW/Cygwin sections
    uname_S := Windows
else                          # If uname not available => 'not' 
    uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
endif

# Avoid nesting "if .. else if .. else .. endif endif"
# because maintenance of matching if/else/endif is a pain

ifeq ($(uname_S),Windows)
    CC := cl 
endif
ifeq ($(uname_S),OSF1)
    CFLAGS += -D_OSF_SOURCE
endif
ifeq ($(uname_S),Linux)
    CFLAGS += -DNDEBUG
endif
ifeq ($(uname_S),GNU/kFreeBSD)
    CFLAGS += -D_BSD_ALLOC
endif
ifeq ($(uname_S),UnixWare)
    CFLAGS += -Wextra
endif
...

请参阅关于uname -sMakefile的完整答案

本答案底部的对应表格来自uname的维基百科文章。请贡献力量,使其保持最新(编辑答案或发表评论)。您也可以更新维基百科文章并发表评论以通知我您的贡献;-)

操作系统 uname -s
Mac OS X Darwin
Cygwin 32位(Win-XP) CYGWIN_NT-5.1
Cygwin 32位(Win-7 32位) CYGWIN_NT-6.1
Cygwin 32位(Win-7 64位) CYGWIN_NT-6.1-WOW64
Cygwin 64位(Win-7 64位) CYGWIN_NT-6.1
MinGW(Windows 7 32位) MINGW32_NT-6.1
MinGW(Windows 10 64位) MINGW64_NT-10.0
Interix(UNIX服务) Interix
MSYS MSYS_NT-6.1
MSYS2 MSYS_NT-10.0-17763
Windows子系统 for Linux Linux
Android Linux
coreutils Linux
CentOS Linux
Fedora Linux
Gentoo Linux
Red Hat Linux Linux
Linux Mint Linux
openSUSE Linux
Ubuntu Linux
Unity Linux Linux
Manjaro Linux Linux
OpenWRT r40420 Linux
Debian(Linux) Linux
Debian(GNU Hurd) GNU
Debian(kFreeBSD) GNU/kFreeBSD
FreeBSD FreeBSD
NetBSD NetBSD
OpenBSD OpenBSD
DragonFlyBSD DragonFly
Haiku Haiku
NonStop NONSTOP_KERNEL
QNX QNX
ReliantUNIX ReliantUNIX-Y
SINIX SINIX-Y
Tru64 OSF1
Ultrix ULTRIX
IRIX 32位 IRIX
IRIX 64位 IRIX64
MINIX Minix
Solaris SunOS
UWIN(64位Windows 7) UWIN-W7
SYS$UNIX:

6
对于Solaris以及相关研究,点赞。 - okutane
5
我支持赞成,就是这样。 - okutane
1
这正是我寻找的,以便编写可移植/跨平台的~/.profile(设置环境变量,如$PATH - 为后人提供搜索关键字的注释)。 - Braham Snyder
5
我来这里是因为我想特别检测WSL,并将其与其他Linux区分开来。那么对我有效的方法是检查 uname -sr 并将其与 Linux*Microsoft) 比较,先于 Linux*) - einarmagnus
1
一个像这样的 case 解决方案通常是最优雅和灵活的(比 if elif 等更好)。我们应该更经常使用它。 - zzapper
显示剩余5条评论

88
Bash设置了shell变量OSTYPE。根据man bash
自动设置为描述bash所执行的操作系统的字符串。
uname相比,这具有微小的优势,因为它不需要启动新进程,所以执行速度更快。
然而,我找不到一个权威的预期值列表。对于我在Ubuntu 14.04上,它被设置为'linux-gnu'。我已经从网上收集了一些其他值。因此:
case "$OSTYPE" in
  linux*)   echo "Linux / WSL" ;;
  darwin*)  echo "Mac OS" ;; 
  win*)     echo "Windows" ;;
  msys*)    echo "MSYS / MinGW / Git Bash" ;;
  cygwin*)  echo "Cygwin" ;;
  bsd*)     echo "BSD" ;;
  solaris*) echo "Solaris" ;;
  *)        echo "unknown: $OSTYPE" ;;
esac

星号在某些情况下很重要 - 例如,OSX在'darwin'后面附加了一个操作系统版本号。据我所知,'win'的值实际上是'win32' - 也许还有一个'win64'?
也许我们可以共同努力填充这里的验证值表:
- Linux Ubuntu(包括WSL):`linux-gnu` - Cygwin 64位:`cygwin` - Msys/MINGW(Windows的Git Bash):`msys` - MacOS Venura:`darwin22.0`
(如果您的值与现有条目不同,请追加您的值)

1
我已经喜欢你的答案胜过我的了,它完美地适用于我偶尔需要这个的情况。 - Charles Roberto Canato
8
严格来说,它不是一个环境变量,而是一个Shell变量。这就是为什么你在 env | grep OSTYPE 下看不到它,但在 set | grep OSTYPE 下可以看到它的原因。 - wisbucky
5
对于有兴趣的人来说,Bash的OSTYPE变量(conftypes.h)是在构建时使用automake的OS变量(Makefile.in)的确切副本进行配置的。可以参考automake的lib/config.sub文件以获取所有可用类型的信息。 - jdknight
2
同时,zsh设置OSTYPE。 - zzapper

13
# This script fragment emits Cygwin rulez under bash/cygwin
if [[ $(uname -s) == CYGWIN* ]];then
    echo Cygwin rulez
else 
    echo Unix is king
fi
如果"uname -s"命令的前6个字符是"CYGWIN",则假定为Cygwin系统。

如果 [ `uname -s` == CYGWIN* ] ,那么看起来更好,而且功能相同。 - David Ferenczy Rogožan
6
是的,但请使用双括号:[[ $(uname -s) == CYGWIN* ]]。同时请注意,在我们的情况下,扩展正则表达式更加精确:[[ $(uname -s) =~ ^CYGWIN* ]] - Andreas Spindler
以上方法效果更好,因为 expr substr $(uname -s) 1 6 在 macOS 上会报错(expr: syntax error)。 - doekman
请注意,扩展正则表达式不是通配符,因此要匹配 CYGWIN 后面的 "任何字符",需要使用 .*。添加 * 只会匹配额外的 N。因此,为了精确匹配,需要使用 [[ $(uname -s) =~ ^CYGWIN.*$ ]],但对于我们的情况,[[ $(uname -s) =~ ^CYGWIN ]] 就足够了。 - jalanb

10

在Albert的回答基础上,我喜欢使用 $COMSPEC 来检测Windows:

#!/bin/bash

if [ "$(uname)" == "Darwin" ]
then
 echo Do something under Mac OS X platform
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]
then
  echo Do something under Linux platform
elif [ -n "$COMSPEC" -a -x "$COMSPEC" ]
then 
  echo $0: this script does not support Windows \:\(
fi

这样可以避免解析$OS的Windows变体名称以及解析像MINGW、Cygwin等的uname变体。

背景: %COMSPEC% 是一个Windows环境变量,用于指定命令处理器的完整路径(也称为Windows shell)。该变量的值通常为%SystemRoot%\system32\cmd.exe,通常会转换为C:\Windows\system32\cmd.exe


由于在Windows UWS Bash环境下运行时$COMSPEC未设置,因此该语句已不再正确。 - Julian Knight

5

https://en.wikipedia.org/wiki/Uname

所有你需要的信息都可以在Google上找到。

使用uname -s来查询系统名称。

  • Mac: Darwin
  • Cygwin: CYGWIN_...
  • Linux: 各种不同,大多数为LINUX

1
我没有足够的声望来进行一个字符的编辑,但是那个维基百科的URL应该更新为指向https版本。能否请一个更高声望的用户来做这件事? - AJM

4
当这个问题被提出时,Windows Subsystem for Linux还不存在。在我的测试中,它给出了以下结果:
uname -s -> Linux
uname -o -> GNU/Linux
uname -r -> 4.4.0-17763-Microsoft

这意味着您需要使用 "uname -r" 命令来区分它与本地 Linux。

1
不幸的是,Mingw-w64 给出了完全相同的结果。 - A Fog

2
好的,这是我的方法。
osis()
{
    local n=0
    if [[ "$1" = "-n" ]]; then n=1;shift; fi

    # echo $OS|grep $1 -i >/dev/null
    uname -s |grep -i "$1" >/dev/null

    return $(( $n ^ $? ))
}

e.g.

osis Darwin &&
{
    log_debug Detect mac osx
}
osis Linux &&
{
    log_debug Detect linux
}
osis -n Cygwin &&
{
    log_debug Not Cygwin
}

我在我的 dotfiles 中使用了这个。

1
我想uname命令的答案是无与伦比的,主要是因为其简洁性。
虽然它需要荒谬的时间来执行,但我发现测试特定文件是否存在也可以给我很好且更快的结果,因为我没有调用可执行文件:
所以,
[ -f /usr/bin/cygwin1.dll ] && echo Yep, Cygwin running
只是使用了一个快速的Bash文件存在检查。由于我现在在Windows上,所以无法从我的头脑中告诉您任何适用于Linux和Mac OS X的特定文件,但我非常确定它们确实存在。 :-)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接