454

I have a shell script that is used both on Windows/Cygwin and Mac and Linux. It needs slightly different variables for each versions.

How can a shell/bash script detect whether it is running in Cygwin, on a Mac or in Linux?

bastibe
  • 16,551
  • 28
  • 95
  • 126

10 Answers10

457

Usually, uname with its various options will tell you what environment you're running in:

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

And, according to the very helpful schot (in the comments), uname -s gives Darwin for OSX and Linux for Linux, while my Cygwin gives CYGWIN_NT-5.1. But you may have to experiment with all sorts of different versions.

So the bash code to do such a check would be along the lines of:

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}

Note that I'm assuming here that you're actually running within CygWin (the bash shell of it) so paths should already be correctly set up. As one commenter notes, you can run the bash program, passing the script, from cmd itself and this may result in the paths not being set up as needed.

If you are doing that, it's your responsibility to ensure the correct executables (i.e., the CygWin ones) are being called, possibly by modifying the path beforehand or fully specifying the executable locations (e.g., /c/cygwin/bin/uname).

Daniel Santos
  • 3,098
  • 26
  • 25
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 14
    Sometimes less is more ;) BTW, Wikipedia has a table of example uname output at http://en.wikipedia.org/wiki/Uname – schot Aug 12 '10 at 09:24
  • 1
    The Git Bash uname -s output on Windows 7 is `MINGW32_NT-6.1`. Also, there is no `/cygdrive` prefix, just `/c` for `C:`. – ColinM Sep 10 '11 at 01:24
  • 9
    Git Bash isn't Cygwin. It is MinGW, minimal GNU for MS Windows, which is why the behavior is different. – Boyd Stephen Smith Jr. Jan 28 '14 at 16:25
  • I promoted the other answer because this answer doesn't deal with the OP portion `How can a shell/bash script detect ...` and the other does. – Jesse Chisholm Jul 12 '17 at 17:48
  • If I run a script under cygwin by executing "\cygwin64\bin\bash -c scriptname", this doesn't necessarily work. In this situation, the cygwin path doesn't get set up and `uname -s` ends up calling whatever `uname` is first in your current path, which on my system turns out to be the version installed with `geda` which returns the text `WindowsNT`. It could equally be the MinGW version as described above, though. A reliable detection for cygwin *must not* rely on the path being set appropriately, IMO. Therefore, `$(uname -s)` should be changed to `$(/bin/uname -s)` to detect cygwin. – Jules Nov 21 '17 at 00:45
  • @Jules, good points but I'm not sure they're relevant. The question talks about running *in* CygWin which I take to mean inside the `bash` shell of it (further supported by the tags). In that case, the paths should already be correctly set. While the path should be able to reach out for Windows executables, they should be *after* all the CygWin stuff. In any case, I'll clarify the answer. – paxdiablo Nov 21 '17 at 01:22
  • 1
    You can run Bash in Windows 10 without cygwin. uname will return something like: MSYS_NT-10.0-19041 so matching against MSYS_NT*) will do the trick. – DoomGoober Nov 10 '20 at 19:48
391

Detect three different OS types (GNU/Linux, Mac OS X, Windows NT)

Notes

  • In your bash script, use #!/usr/bin/env bash instead of #!/bin/sh to prevent the problem caused by /bin/sh linked to different default shell in different platforms, or there will be error like unexpected operator, that's what happened on my computer (Ubuntu 64 bits 12.04).
  • Mac OS X 10.6.8 (Snow Leopard) do not have expr program unless you install it, so I just use uname.

Design

  1. Use uname to get the system information (-s parameter).
  2. Use expr and substr to deal with the string.
  3. Use if elif fi to do the matching job.
  4. You can add more system support if you want, just follow the uname -s specification.

Implementation

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

Testing

  • Linux (Ubuntu 12.04 LTS, Kernel 3.2.0) tested OK.
  • OS X (10.6.8 Snow Leopard) tested OK.
  • Windows (Windows 7 64 bit) tested OK.

What I learned

  1. Check for both opening and closing quotes.
  2. Check for missing parentheses and braces {}

References

hoijui
  • 3,615
  • 2
  • 33
  • 41
Albert
  • 10,377
  • 4
  • 22
  • 27
  • For MinGW, it may make more sense to check: `[ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]`. – Achal Dave Jul 13 '13 at 20:27
  • 2
    @Albert: expression `"$(expr substr $(uname -s) 1 5)"` is weird a bit. There are more pretty ways to do that, for example: `if [ \`uname -s\` == CYGWIN* ]; then`. Read it: if `uname -s` starts with *CYGWIN* then... – David Ferenczy Rogožan May 14 '14 at 15:06
  • 12
    @DawidFerenczy I believe it would require double brackets like `if [[ $(uname -s) == CYGWIN* ]]; then ` – rogerdpack Aug 31 '15 at 20:58
  • 1
    This doesn't detect Cygwin, as was asked in the question. – rmcclellan Oct 25 '16 at 15:49
  • 2
    Why does this check only the first 5 characters for Linux? Are there examples of modern Linux distros where `uname -s` will yield something other than "Linux"? – ktbiz Dec 15 '16 at 17:37
  • On 64-bit Windows 10, the third case should read `MINGW64_NT` instead of `MINGW32_NT`. – Craig Otis Mar 08 '17 at 23:27
  • @CraigOtis I do not have the environment to test the new modification, if the new codes have bugs, corrections are welcomed. – Albert Mar 19 '17 at 07:39
  • ShellCheck will complain about expr usage: https://github.com/koalaman/shellcheck/wiki/SC2003 – Deiwin Jun 04 '20 at 14:30
189

Use uname -s (--kernel-name) because uname -o (--operating-system) is not supported on some Operating Systems such as Mac OS and Solaris. You may also use just uname without any argument since the default argument is -s (--kernel-name).

To distinguish WSL from Linux, einarmagnus recommends uname -sr (--kernel-name --kernel-release) as proposed in the following script.

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

The following Makefile is inspired from Git project (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
...

See also this complete answer about uname -s and Makefile.

The correspondence table in the bottom of this answer is from Wikipedia article about uname. Please contribute to keep it up-to-date (edit the answer or post a comment). You may also update the Wikipedia article and post a comment to notify me about your contribution ;-)

Operating System uname -s
Mac OS X Darwin
Cygwin 32-bit (Win-XP) CYGWIN_NT-5.1
Cygwin 32-bit (Win-7 32-bit) CYGWIN_NT-6.1
Cygwin 32-bit (Win-7 64-bit) CYGWIN_NT-6.1-WOW64
Cygwin 64-bit (Win-7 64-bit) CYGWIN_NT-6.1
MinGW (Windows 7 32-bit) MINGW32_NT-6.1
MinGW (Windows 10 64-bit) MINGW64_NT-10.0
Interix (Services for UNIX) Interix
MSYS MSYS_NT-6.1
MSYS2 MSYS_NT-10.0-17763
Windows Subsystem 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 bits IRIX
IRIX 64 bits IRIX64
MINIX Minix
Solaris SunOS
UWIN (64-bit Windows 7) UWIN-W7
SYS$UNIX:SH on OpenVMS IS/WB
z/OS USS OS/390
Cray sn5176
(SCO) OpenServer SCO_SV
(SCO) System V SCO_SV
(SCO) UnixWare UnixWare
IBM AIX AIX
IBM i with QSH OS400
HP-UX HP-UX
oHo
  • 51,447
  • 27
  • 165
  • 200
  • 6
    Thumbs up for Solaris and research above. – okutane Feb 06 '17 at 14:15
  • Hi @okutane. I do not understand what you mean. Please provide more details. Do you suggest something? Cheers – oHo Feb 10 '17 at 10:32
  • 5
    I'm voting up, that's it. – okutane Feb 10 '17 at 10:36
  • 1
    This is precisely what I was looking for in order to write a portable/cross-platform `~/.profile` (to set environment variables like `$PATH` -- commenting to provide search keywords for posterity). – Braham Snyder Oct 01 '17 at 16:36
  • Should probably add Windows Subsystem for Linux now – Lime Aug 16 '18 at 01:46
  • Thank you @William for your feedback. I do not have *Windows Subsystem for Linux* but I have found this [`uname` sample](https://linuxhint.com/install_windows_subsystem_linux/). I suppose `uname -s` displays `Linux` on *Windows Subsystem for Linux*, isn't it? Can you confirm? – oHo Aug 20 '18 at 12:39
  • This is what ubuntu subsystem for linux displays for me Linux uname -a `DESKTOP-9ETGU1K 4.4.0-17134-Microsoft #285-Microsoft Thu Aug 30 17:31:00 PST 2018 x86_64 x86_64 x86_64 GNU/Linux` – Lime Sep 30 '18 at 01:38
  • Thank you @William I have just added "Windows Subsystem for Linux" in the table. Cheers – oHo Oct 01 '18 at 07:07
  • 5
    I came here because I wanted to detect WSL specifically, and differentiate it from other Linuxes. What seems to work for me then is to check `uname -sr` and compare against `Linux*Microsoft)` before `Linux*)`. – einarmagnus May 06 '20 at 10:13
  • 1
    A case solution as shown here is usually the most elegant & flexible (better than if elif etc). must use it more often – zzapper Aug 26 '21 at 09:52
85

Bash sets the shell variable OSTYPE. From man bash:

Automatically set to a string that describes the operating system on which bash is executing.

This has a tiny advantage over uname in that it doesn't require launching a new process, so will be quicker to execute.

However, I'm unable to find an authoritative list of expected values. For me on Ubuntu 14.04 it is set to 'linux-gnu'. I've scraped the web for some other values. Hence:

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

The asterisks are important in some instances - for example OSX appends an OS version number after the 'darwin'. The 'win' value is actually 'win32', I'm told - maybe there is a 'win64'?

Perhaps we could work together to populate a table of verified values here:

  • Linux Ubuntu (incl. WSL): linux-gnu
  • Cygwin 64-bit: cygwin
  • Msys/MINGW (Git Bash for Windows): msys

(Please append your value if it differs from existing entries)

wisbucky
  • 33,218
  • 10
  • 150
  • 101
Jonathan Hartley
  • 15,462
  • 9
  • 79
  • 80
  • 1
    I already like your answer more than mine, fits perfectly on the rare times I've needed this – Charles Roberto Canato May 25 '16 at 23:43
  • 7
    Technically, it's not an environment variable, it's a shell variable. That's why you won't see it under `env | grep OSTYPE`, but you will see it under `set | grep OSTYPE` – wisbucky Feb 16 '17 at 00:20
  • 5
    For those interested, Bash's `OSTYPE` variable [(conftypes.h)](http://git.savannah.gnu.org/cgit/bash.git/tree/conftypes.h) is configured at build time using the exact copy of automake's `OS` variable [(Makefile.in)](http://git.savannah.gnu.org/cgit/bash.git/tree/Makefile.in?id=a0c0a00fc419b7bc08202a79134fcd5bc0427071#n145). One may consult automake's [lib/config.sub](http://git.savannah.gnu.org/cgit/automake.git/tree/lib/config.sub) file for all of the available types. – jdknight Mar 25 '17 at 19:58
  • 1
    also zsh sets OSTYPE – zzapper Aug 26 '21 at 09:56
13
# This script fragment emits Cygwin rulez under bash/cygwin
if [[ $(uname -s) == CYGWIN* ]];then
    echo Cygwin rulez
else 
    echo Unix is king
fi

If the 6 first chars of uname -s command is "CYGWIN", a cygwin system is assumed

doekman
  • 18,750
  • 20
  • 65
  • 86
Jan Helge
  • 139
  • 1
  • 3
  • `if [ \`uname -s\` == CYGWIN* ]; then` looks better and works the same. – David Ferenczy Rogožan May 14 '14 at 15:09
  • 6
    Yes, but use double brackets: `[[ $(uname -s) == CYGWIN* ]]`. Note also that extended regular expressions are more precise in our case: `[[ $(uname -s) =~ ^CYGWIN* ]]`. – Andreas Spindler Dec 30 '14 at 04:19
  • Above works better, because `expr substr $(uname -s) 1 6` gives an error (`expr: syntax error`) on macOS. – doekman Sep 20 '18 at 09:33
  • Note that extended regular expressions are not globs, so to match "any char" after the `CYGWIN` requires `.*`. Adding `*` will only match extra `N`s. So `[[ $(uname -s) =~ ^CYGWIN.*$ ]]` is needed for precision, but for our case `[[ $(uname -s) =~ ^CYGWIN ]]` would suffice – jalanb Jun 09 '21 at 11:58
10

To build upon Albert's answer, I like to use $COMSPEC for detecting 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

This avoids parsing variants of Windows names for $OS, and parsing variants of uname like MINGW, Cygwin, etc.

Background: %COMSPEC% is a Windows environmental variable specifying the full path to the command processor (aka the Windows shell). The value of this variable is typically %SystemRoot%\system32\cmd.exe, which typically evaluates to C:\Windows\system32\cmd.exe .

Steve Jansen
  • 9,398
  • 2
  • 29
  • 34
5

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

All the info you'll ever need. Google is your friend.

Use uname -s to query the system name.

  • Mac: Darwin
  • Cygwin: CYGWIN_...
  • Linux: various, LINUX for most
Markus
  • 3,447
  • 3
  • 24
  • 26
rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • 1
    I don't have enough rep for a 1-character edit, but that Wikipedia URL should be updated to point to the https version. Can a higher-rep user please do that? – AJM Feb 09 '22 at 19:33
4

Windows Subsystem for Linux did not exist when this question was asked. It gave these results in my test:

uname -s -> Linux
uname -o -> GNU/Linux
uname -r -> 4.4.0-17763-Microsoft

This means that you need uname -r to distinguish it from native Linux.

A Fog
  • 4,360
  • 1
  • 30
  • 32
2

Ok, here is my way.

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
}

I use this in my dotfiles

wener
  • 7,191
  • 6
  • 54
  • 78
1

I guess the uname answer is unbeatable, mainly in terms of cleanliness.

Although it takes a ridiculous time to execute, I found that testing for specific files presence also gives me good and quicker results, since I'm not invoking an executable:

So,

[ -f /usr/bin/cygwin1.dll ] && echo Yep, Cygwin running

just uses a quick Bash file presence check. As I'm on Windows right now, I can't tell you any specific files for Linuxes and Mac OS X from my head, but I'm pretty sure they do exist. :-)