DrKabob
DrKabob

Reputation: 481

How to check if a program is run in Bash on Ubuntu on Windows and not just plain Ubuntu?

Pretty straightforward, the usual places to figure out the OS you're on seem to be identical to plain Ubuntu on Ubuntu for Windows. For example uname -a is identical to a native GNU/Linux install and /etc/os-version is identical to a Ubuntu Trusty Tahr install.

The only thing I can think of is to check if /mnt/c/Windows exists, but I'm not sure if that's a foolproof idea.

Upvotes: 48

Views: 13921

Answers (15)

NotTheDr01ds
NotTheDr01ds

Reputation: 20795

Lots of answers here over the years, and they'll work most of the time. But every answer here has the potential to return false positives or false negatives in certain situations, and users should be aware of these so they choose the best method for their use-case.

First, IMHO, the method with the best chance. That is, it is unlikely to return a false positive or a false negative:

  • The presence of /proc/sys/fs/binfmt_misc/WSLInterop is a pretty good indicator that you are on WSL. This may be the most reliable method, and it is what Ubuntu's Snapd project uses as its detection mechanism. This file exists under both WSL1 and WSL2 by default. Even when Interop is disabled via /etc/wsl.conf, this file will still be created by WSL at startup.

    Caveats: Of course, a binfmt_misc entry could be set up with the name WSLInterop, but that would be extremely pathologic, leading to a false-positive test.

    Also, it is possible to override the name of the Interop, as mentioned in my Ask Ubuntu answer here. This would be an unusual case, but we used it to thwart the Snapd WSL detection temporarily while a bug was being fixed. This creates a false negative, of course.

    In a war of escalation, you could "thwart-the-thwarting" by grepping for the magic 4d5a in that directory, but that might be going a bit far ;-)

  • A close second, to me, is checking for the presence of both the strings "Microsoft" and "WSL" in the kernel name. This can be done most easily on a Systemd distribution (even without Systemd being enabled) using:

    [[ "$(systemd-detect-virt --container)" -eq "wsl" ]] && echo "WSL" || echo "Not WSL"
    

    On both WSL1 and WSL2, this returns command returns wsl. The source shows that it uses checks the kernel name for the strings "Microsoft" and "WSL". It also references this Github comment from the WSL lead recommending the technique.

    As far as reliability goes, it would be pathologic for someone to create a kernel with those names that wasn't intended to be used with WSL, so it has a very low chance of presenting a false positive.

    However, it is entirely possible to compile your own kernel for WSL2, and there's no guarantee that you will name it with "Microsoft" and "WSL". In fact, I tend to name mine differently so that I know I'm not using the stock WSL2 kernel. This will create a false negative for the above check. And while you might think you'll remember this, it can be a pain to troubleshoot things like this when you forget that you have a custom kernel installed (I speak from experience).

    Note that uname -a, /proc/version, and other similar checks are just a variation on the test above, with the same caveats. Note that there nine answers here (10 with mine) that use some variation of this technique already, so please, if you are going to add another, make sure it is truly unique ;-).

Credit goes to @Massimo's answer for combining the above two methods into what is probably the best existing answer here. It probably just needed some more explanation on why it's an improvement.


Other methods and their corner cases:

  • Relying on $WSL_DISTRO_NAME has two issues:

    • It won't exist for a root user.

    • It won't exist in any shell started through a PAM login. For instance:

      $ [[ -n $WSL_DISTRO_NAME ]] && echo "WSL" || echo "Not WSL"
      WSL
      $ su - $USER
      # login
      $ [[ -n $WSL_DISTRO_NAME ]] && echo "WSL" || echo "Not WSL"
      Not WSL
      

    So there's a rather high chance of receiving a false negative. On the other hand, creating a false positive by falsifying a $WSL_DISTRO_NAME variable would be pathologic.

  • Relying on $IS_WSL is just plain wrong. That's a variable that is set by Kali Linux only and does not exist in most WSL distributions, including the default Ubuntu installation.

  • Testing for the presence of some known executable (such as wsl.exe or explorer.exe) has the following potential problems:

    • Interop can be disabled. However, if you are doing this on your own system, then you'll know whether or not this is the case. If you are trying to create a script which will run on other users' systems, then you probably won't want to use this due to the chance of false negatives.

    • Interop can fail under certain scenarios. For instance, similar to the $WSL_DISTRO_NAME above, a su - $USER will create a PAM login session where the Interop will not work.

Upvotes: 2

A Merii
A Merii

Reputation: 594

Shorter cleaner version of @Shital Shah's answer.

[ -n "$IS_WSL" ] || [ -n "$WSL_DISTRO_NAME" ] && echo 'wsl' || echo 'anything else'

Upvotes: 1

Massimo
Massimo

Reputation: 3470

A fail-proof test:

grep -qi -- '-WSL' /proc/sys/kernel/osrelease || test -f /proc/sys/fs/binfmt_misc/WSLInterop

Rationale:

  1. Parsing /proc/version is dangerous. It could contain misleading info (imagine a gcc compiled by microsoft). It is better to parse only the kernel release.
  2. WSL2: there aren't any WSL... environment variables. (WINDOWS 10 20H2 build 19042.1165, UBUNTU 18.04.5 LTS, kernel 5.10.16.3-microsoft-standard-WSL2)
  3. In case the test of the kernel release fails, there is a second test.

Note: having two tests, from the first one I removed microsoft and grep only on -WSL. In this simplest form, it is almost fail-proof.

The binfmt_misc template file (to run Windows executables under linux) exists both on WSL and WSL2.

Upvotes: 1

Jose Sa
Jose Sa

Reputation: 31

Since the distinction between WSL1 and WSL2 is that the first runs inside a container while the second runs in a virtual machine, we can make use of "systemd-detect-virt --container" to differentiate from both environments.

if [ -n "${WSL_DISTRO_NAME}" ]; then
  # In WSL but which one?
  virt_container="$(systemd-detect-virt --container)"
  case ${virt_container} in
    wsl)
      echo "This is WSL 1"
      ;;
    none)
      echo "This is WSL 2"
      ;;
    *)
      echo "Don't known ${virt_container}"
      ;;
  esac
fi

Upvotes: 1

user3073309
user3073309

Reputation: 186

if [[ `uname -a | grep -i linux | grep -i microsoft`  != "" ]]; then echo "microsoft wsl"; fi;

Or multi-line syntax:

if [[ `uname -a | grep -i linux | grep -i microsoft`  != "" ]]; then 
    echo "microsoft wsl" 
fi

Note: The conditions have to be wrapped in the backticks or it will produce errors such as:

zsh: parse error: condition expected: uname

Upvotes: 3

Shital Shah
Shital Shah

Reputation: 68858

Updating answer by @per-lundberg:

if [[ -n "$IS_WSL" || -n "$WSL_DISTRO_NAME" ]]; then
    echo "This is WSL"
else
    echo "This is not WSL"
fi

Note: IS_WSL existed in older versions (using lxrun) while WSL_DISTRO_NAME exists in current versions (from Microsoft Store).

Upvotes: 24

Gary S. Weaver
Gary S. Weaver

Reputation: 8106

The following works in bash on Windows 10, macOS, and Linux:

#!/bin/bash
set -e
if grep -qEi "(Microsoft|WSL)" /proc/version &> /dev/null ; then
    echo "Windows 10 Bash"
else
    echo "Anything else"
fi

You need to check for both "Microsoft" and "WSL" per this comment by Ben Hillis, WSL Developer:

For the time being this is probably the best way to do it. I can't promise that we'll never change the content of these ProcFs files, but I think it's unlikely we'll change it to something that doesn't contain "Microsoft" or "WSL".

/proc/sys/kernel/osrelease
/proc/version

And case shall be ignored for grep. In WSL2, /proc/version gives lowercased microsoft.

Upvotes: 47

GollyJer
GollyJer

Reputation: 26752

I needed to test for macOS in addition to Windows Subsystem for Linux 2.

This is the simplest thing working for us.

if [[ $OSTYPE == darwin* ]]; then
  # macOS
elif [[ "$(</proc/sys/kernel/osrelease)" == *microsoft* ]]; then
  # WSL2
else
  # Other *nix distro.
fi

NOTE: The if order matters. On macOS you get this error when looking at proc/version.
/proc/version: No such file or directory


hat-tip @Niklas Holm and @Marc Cornellà in the top answer's comments for aiming me toward the correct WSL check.

Upvotes: 2

march_happy
march_happy

Reputation: 450

For WSL2, we can no longer detect through kernel version because it is running an actual Linux kernel in Hyper-V. However, it still can call explorer.exe existing in every Windows installation. So we could...

if [ -x "$(command -v explorer.exe)" ]; then
echo "We are running on WSL"
fi

This should be a more generic way to detect if the script is running on WSL.

Edit: See answers above. I forgot to count Unix-like environments like Msys2 in.

Upvotes: 2

Per Lundberg
Per Lundberg

Reputation: 4220

Without me doing anything special, these environment variables seem to be set already:

$ set | grep WSL
IS_WSL='Linux version 4.4.0-18362-Microsoft ([email protected]) (gcc version 5.4.0 (GCC) ) #1-Microsoft Mon Mar 18 12:02:00 PST 2019'
WSLENV=
WSL_DISTRO_NAME=Debian

So, something like the following snippet should also work in this case (example of what I used it for myself):

if [ ! -z "$IS_WSL" ]; then
    alias code='/mnt/c/Users/per/AppData/Local/Programs/Microsoft\ VS\ Code/Code.exe'
fi

(Note that technically, -z does not check if the variable is unset, merely that it is empty; in practice, this works well enough in this case. The ! at the beginning is there to negate the check.)

Upvotes: 4

gregln
gregln

Reputation: 1

Windows Subsystem for Linux 2 (WSL 2) in Windows 10 Pro Insider Preview Build 18917

/proc/version contains:

Linux version 4.19.43-microsoft-standard (oe-user@oe-host) (gcc version 7.3.0 (GCC)) #1 SMP...

Upvotes: 0

gavenkoa
gavenkoa

Reputation: 48893

If you in Bash and want to avoid fork:

is_wsl=0
read os </proc/sys/kernel/osrelease || :
if [[ "$os" == *Microsoft ]]; then
  is_wsl=1
fi

Upvotes: 0

Dan
Dan

Reputation: 2165

Here's what I put in my .bashrc

if [[ $(uname -v | sed -rE 's/^#[0-9]{3,}-(\S+).+/\1/') == "Microsoft" ]]; then
  # WSL-specific code
fi
  • uname -v gets the kernel version in the format of #379-Microsoft Wed Mar 06 19:16:00 PST 2019 and the sed expression pulls out the Microsoft string.

Upvotes: 0

Ryan
Ryan

Reputation: 425

I just came up with this for my .bashrc for adding some WSL items to $PATH.

Works in 1703. Not sure if earlier versions.

if [[ $(uname -r) =~ Microsoft$ ]]; then
    foo
fi

Upvotes: 6

Adno
Adno

Reputation: 223

I've been looking for ways to detect that as well. So far I've found 2.

  • /proc/sys/kernel/osrelease is "3.4.0-Microsoft"

  • /proc/version is "Linux version 3.4.0-Microsoft ([email protected]) (gcc version 4.7 (GCC) ) #1 SMP PREEMPT Wed Dec 31 14:42:53 PST 2014"

If you just use the Ubuntu distribution installed by default there should be no problems with using them, as they said that it would be unlikely for them to set either to something that doesn't contain "Microsoft" or "WSL".

However, if you were to install a different Linux distribution, I'm pretty sure that the contents of /proc/sys/kernel/osrelease and /proc/version will change, since the distro wouldn't have been compiled by Microsoft.

Upvotes: 9

Related Questions