﻿---
title: Nushell - Cross-Platform
date: 2025-10-16
excerpt: Encapsulating cross-platform functions and isolating OS-specific configs using Nushell.
tags:
  - Linux
  - OS
  - macOS
  - Windows
  - Shell
  - Nushell
cover: https://assets.vluv.space/cover/nushell_cross_platform.avif
updated: 2026-05-23 15:36:36
lang: en
i18n:
  cn: /nu_cross_platform
  translation: 2
---

<script data-swup-reload-script type="module" src="/js/components/tab.js"></script>

In Bash, you can detect the OS using the `$OSTYPE` variable[^1]:

```bash
case "$OSTYPE" in
  solaris*) echo "SOLARIS" ;;
  darwin*)  echo "OSX" ;;
  linux*)   echo "LINUX" ;;
  bsd*)     echo "BSD" ;;
  msys*)    echo "WINDOWS" ;;  # via Git Bash/msysGit
  cygwin*)  echo "ALSO WINDOWS" ;; # via Cygwin
  *)        echo "unknown: $OSTYPE" ;;
esac
```

Detecting Windows is a bit cumbersome this way. You could use `uname` instead, but that introduces a new dependency.

Nushell, being a more modern shell, handles this more conveniently. It stores OS-related information — system name, architecture, etc. — in the `os-info` field of the `$nu`[^2] constant:

```nu
# Print $nu.os-info
╭────────────────┬─────────╮
│ name           │ macos   │
│ arch           │ aarch64 │
│ family         │ unix    │
│ kernel_version │ 25.1.0  │
╰────────────────┴─────────╯
```

## Demos

Using this field as a condition, you can easily write cross-platform functions/scripts and keep OS-specific configurations separate.

### Cross-Platform Functions/Scripts

Basic commands differ across operating systems (e.g., opening the file manager). You can use `match $nu.os-info.name` to wrap a unified interface that hides platform differences.

<x-tabs>

<x-tab title="File_Explorer" active>

Take opening the file manager: Windows uses `explorer.exe`, while macOS uses `Finder.app`. You can write a function that opens the specified folder with the platform's default file manager:

```nu config.nu
def omni-open [path: string = "."] {
  let path = ($path | path expand)

  match $nu.os-info.name {
    macos => { ^open $path }
    windows => { explorer.exe $path }
    linux => { ^xdg-open $path } # not tested
  }
}

alias o = omni-open
```

By the way, Nushell's built-in `start` command already does what the function above does, and also supports opening URLs, Obsidian vaults, etc.

<video autoplay loop muted playsinline>
   <source src="https://assets.vluv.space/assets-PixPin_2025-10-15_23-53-20.mp4" type="video/mp4" alt="demo">
</video>

</x-tab>

<x-tab title="Clipboard">

Clipboard commands also differ across operating systems — `pbcopy` on macOS, `clip` on Windows. You can write a function to copy text cross-platform:

```nu config.nu
def copy_text [] {
  match $nu.os-info.name {
    "windows" => {
      # Windows uses the clip command
      $in | ^clip
    }
    "macos" => {
      # macOS uses the pbcopy command
      $in | ^pbcopy
    }
  }
}
```

Usage examples:

```nu
# Copy current path
pwd | copy_text
# Copy a file's contents
open README.md | copy_text
```

</x-tab>

<x-tab title="envir_vars">

Environment variable setup also varies by platform: `launchctl setenv` on macOS, `setx` on Windows, and writing to `/etc/profile.d/**.sh` on Linux. See [[Unix环境变量]] for details. You can write a Nushell function to encapsulate these differences:

```nu
def --env env [name: string value: string] {
  # Set env var for current session
  load-env {$name: $value}
  match $nu.os-info.name {
    "macos" => {
      /bin/launchctl setenv $name $value
    }
    "linux" => {
      let env_file = "/etc/profile.d/gnix-env.sh"
      touch $env_file
      chmod +x $env_file
      let lines = (open $env_file | lines | where not ($it | str starts-with $"export ($name)="))
      let env_value = ($value | str replace $nu.home-path "$HOME")
      let updated = ($lines | append $"export ($name)=\"($env_value)\"")
      $updated | str join (char nl) | save --force $env_file
    }
    "windows" => {
      setx $name $value
    }
  }
}
```

</x-tab>

</x-tabs>

### OS-Specific Configs

Shell config files often contain OS-specific content — Scoop aliases on Windows, Homebrew completion scripts on macOS, etc. PATH settings also vary by system.

You can handle this with conditional logic in `config.nu`:

#### Step 1: OS-specific Config

For Windows, create `platform/win.nu` to hold platform-specific config:

 ```nu platform/win.nu
$env.YAZI_FILE_ONE = 'D:/envir_vars/scoop/apps/git/current/usr/bin/file.exe'

source ../aliases/scoop.nu
source ../completions/scoop.nu
source ../completions/winget.nu

def --env pwd [] {
  $env.PWD | str replace --all '\' '/'
}

alias su = scoop update
alias si = scoop install
alias sui = scoop uninstall
alias sse = scoop search
alias sst = scoop status
alias sl = scoop list
alias sbl = scoop bucket list
alias sba = scoop bucket add
alias sbr = scoop bucket rm
alias sui = scoop uninstall
alias cdc = cd c://
alias cdd = cd d://
alias cde = cd e://
```

#### Step 2: Conditional Load

In `config.nu`, write it like this — using Nushell's `source null`[^3] feature as a no-op:

```nu config.nu
const window_module = if $nu.os-info.name == windows { "./platform/win.nu" } else { null }
const mac_module = if $nu.os-info.name == macos { "./platform/mac.nu" } else { null }

source $window_module
source $mac_module
```

[^1]: See [Bash Variables (Bash Reference Manual)](https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html)
[^2]: [Special Variables | Nushell](https://www.nushell.sh/book/special_variables.html#nu)
[^3]: [source | Nushell](https://www.nushell.sh/commands/docs/source.html) In Nushell, sourcing `null` is a no-op.