Modules

Similar to many other programming languages, Nushell also has modules to organize your code. Each module is a "bag" containing a bunch of definitions (typically commands) that you can export (take out of the bag) and use in your current scope. Since Nushell is also a shell, modules allow you to modify environment variables when importing them.

Quick Overview

There are three ways to define a module in Nushell:

  1. "inline"
    • module spam { ... }
  2. from a file
    • using a .nu file as a module
  3. from a directory
    • directory must contain a mod.nu file

In Nushell, creating a module and importing definitions from a module are two different actions. The former is done using the module keyword. The latter using the use keyword. You can think of module as "wrapping definitions into a bag" and use as "opening a bag and taking definitions from it". In most cases, calling use will create the module implicitly, so you typically don't need to use module that much.

You can define the following things inside a module:

  • Commands* (def)
  • Aliases (alias)
  • Known externals* (extern)
  • Submodules (module)
  • Imported symbols from other modules (use)
  • Environment setup (export-env)

Only definitions marked with export are possible to access from outside of the module ("take out of the bag"). Definitions not marked with export are allowed but are visible only inside the module (you could call them private). (export-env is special and does not require export.)

*These definitions can also be named main (see below).

"Inline" modules

The simplest (and probably least useful) way to define a module is an "inline" module can be defined like this:

module greetings {
    export def hello [name: string] {
        $"hello ($name)!"
    }

    export def hi [where: string] {
        $"hi ($where)!"
    }
}

use greetings hello

hello "world"

You can paste the code into a file and run it with nu, or type into the REPL.

First, we create a module (put hello and hi commands into a "bag" called greetings), then we import the hello command from the module (find a "bag" called greetings and take hello command from it) with use.

Modules from files

A .nu file can be a module. Just take the contents of the module block from the example above and save it to a file greetings.nu. The module name is automatically inferred as the stem of the file ("greetings").

# greetings.nu

export def hello [name: string] {
    $"hello ($name)!"
}

export def hi [where: string] {
    $"hi ($where)!"
}

then

> use greetings.nu hello

> hello

The result should be similar as in the previous section.

Note that the use greetings.nu hello call here first implicitly creates the greetings module, then takes hello from it. You could also write it as module greetings.nu, use greetings hello. Using module can be useful if you're not interested in any definitions from the module but want to, e.g., re-export the module (export module greetings.nu).

Modules from directories

Finally, a directory can be imported as a module. The only condition is that it needs to contain a mod.nu file (even empty, which is not particularly useful, however). The mod.nu file defines the root module. Any submodules (export module) or re-exports (export use) must be declared inside the mod.nu file. We could write our greetings module like this:

In the following examples, / is used at the end to denote that we're importing a directory but it is not required.

# greetings/mod.nu

export def hello [name: string] {
    $"hello ($name)!"
}

export def hi [where: string] {
    $"hi ($where)!"
}

then

> use greetings/ hello

> hello

The name of the module follows the same rule as module created from a file: Stem of the directory name, i.e., the directory name, is used as the module name. Again, you could do this as a two-step action using module and use separately, as explained in the previous section.

TIP

You can define main command inside mod.nu to create a command named after the module directory.

Import Pattern

Anything after the use keyword forms an import pattern which controls how the definitions are imported. The import pattern has the following structure use head members... where head defines the module (name of an existing module, file, or directory). The members are optional and specify what exactly should be imported from the module.

Using our greetings example:

use greetings

imports all symbols prefixed with the greetings namespace (can call greetings hello and greetings hi).

use greetings hello

will import the hello command directly without any prefix.

use greetings [hello, hi]

imports multiple definitions<> directly without any prefix.

use greetings *

will import all names directly without any prefix.

main

Exporting a command called main from a module defines a command named as the module. Let's extend our greetings example:

# greetings.nu

export def hello [name: string] {
    $"hello ($name)!"
}

export def hi [where: string] {
    $"hi ($where)!"
}

export def main [] {
    "greetings and salutations!"
}

then

> use greetings.nu

> greetings
greetings and salutations!

> greetings hello world
hello world!

The main is exported only when

  • no import pattern members are used (use greetings.nu)
  • glob member is used (use greetings.nu *)

Importing definitions selectively (use greetings.nu hello or use greetings.nu [hello hi]) does not define the greetings command from main. You can, however, selectively import main using use greetings main (or [main]) which defines only the greetings command without pulling in hello or hi.

Apart from commands (def, def --env), known externals (extern) can also be named main.

Submodules and subcommands

Submodules are modules inside modules. They are automatically created when you call use on a directory: Any .nu files inside the directory are implicitly added as submodules of the main module. There are two more ways to add a submodule to a module:

  1. Using export module
  2. Using export use

The difference is that export module some-module only adds the module as a submodule, while export use some-module also re-exports the submodule's definitions. Since definitions of submodules are available when importing from a module, export use some-module is typically redundant, unless you want to re-export its definitions without the namespace prefix.

Notemodule without export defines only a local module, it does not export a submodule.

Let's illustrate this with an example. Assume three files:

# greetings.nu

export def hello [name: string] {
    $"hello ($name)!"
}

export def hi [where: string] {
    $"hi ($where)!"
}

export def main [] {
    "greetings and salutations!"
}
# animals.nu

export def dog [] {
    "haf"
}

export def cat [] {
    "meow"
}
# voice.nu

export use greetings.nu *

export module animals.nu

Then:

> use voice.nu

> voice animals dog
haf

> voice animals cat
meow

> voice hello world
hello world

> voice hi there
hi there!

> voice greetings
greetings and salutations!

As you can see, defining the submodule structure also shapes the command line API. In Nushell, namespaces directly folds into subcommands. This is true for all definitions: aliases, commands, known externals, modules.

Environment Variables

Modules can also define an environment using export-env:

# greetings.nu

export-env {
    $env.MYNAME = "Arthur, King of the Britons"
}

export def hello [] {
    $"hello ($env.MYNAME)"
}

When use is evaluated, it will run the code inside the export-env block and merge its environment into the current scope:

> use greetings.nu

> $env.MYNAME
Arthur, King of the Britons

> greetings hello
hello Arthur, King of the Britons!

TIP

You can put a complex code defining your environment without polluting the namespace of the module, for example:

    def tmp [] { "tmp" }
    def other [] { "other" }

    let len = (tmp | str length)

    load-env {
        OTHER_ENV: (other)
        TMP_LEN: $len
    }
}

Only $env.TMP_LEN and $env.OTHER_ENV are preserved after evaluating the export-env module.

Caveats

Like any programming language, Nushell is also a product of a tradeoff and there are limitations to our module system.

export-env runs only when the use call is evaluated

If you also want to keep your variables in separate modules and export their environment, you could try to export use it:

# purpose.nu
export-env {
    $env.MYPURPOSE = "to build an empire."
}

export def greeting_purpose [] {
    $"Hello ($env.MYNAME). My purpose is ($env.MYPURPOSE)"
}

and then use it

> use purpose.nu

> purpose greeting_purpose

However, this won't work, because the code inside the module is not evaluated, only parsed (only the export-env block is evaluated when you call use purpose.nu). To export the environment of greetings.nu, you need to add it to the export-env module:

# purpose.nu
export-env {
    use greetings.nu
    $env.MYPURPOSE = "to build an empire."
}

export def greeting_purpose [] {
    $"Hello ($env.MYNAME). My purpose is ($env.MYPURPOSE)"
}

then

> use purpose.nu

> purpose greeting_purpose
Hello Arthur, King of the Britons. My purpose is to build an empire.

Calling use purpose.nu ran the export-env block inside purpose.nu which in turn ran use greetings.nu which in turn ran the export-env block inside greetings.nu, preserving the environment changes.

Module file / command cannot be named after parent module

  • Module directory cannot contain .nu file named after the directory (spam/spam.nu)
    • Consider a spam directory containing both spam.nu and mod.nu, calling use spam * would create an ambiguous situation where the spam module would be defined twice.
  • Module cannot contain file named after the module
    • Same case as above: Module spam containing both main and spam commands would create an ambiguous situation when exported as use spam *.

Examples

This section describes some useful patterns using modules.

Local Definitions

Anything defined in a module without the export keyword will work only in the module's scope.

# greetings.nu

use tools/utils.nu generate-prefix  # visible only locally (we assume the file exists)

export def hello [name: string] {
    greetings-helper "hello" "world"
}

export def hi [where: string] {
    greetings-helper "hi" "there"
}

def greetings-helper [greeting: string, subject: string] {
    $"(generate-prefix)($greeting) ($subject)!"
}

then

> use greetings.nu *


> hello "world"
hello world!

> hi "there"
hi there!

> greetings-helper "foo" "bar"  # fails because 'greetings-helper' is not exported

> generate-prefix  # fails because the command is imported only locally inside the module

Dumping files into directory

A common pattern in traditional shells is dumping and auto-sourcing files from a directory (for example, loading custom completions). In Nushell, doing this directly is currently not possible, but directory modules can still be used.

Here we'll create a simple completion module with a submodule dedicated to some Git completions:

  1. Create the completion directory mkdir ($nu.default-config-dir | path join completions)
  2. Create an empty mod.nu for it touch ($nu.default-config-dir | path join completions mod.nu)
  3. Put the following snippet in git.nu under the completions directory
export extern main [
    --version(-v)
    -C: string
    # ... etc.
]

export extern add [
    --verbose(-v)
    --dry-run(-n)
    # ... etc.
]

export extern checkout [
    branch: string@complete-git-branch
]

def complete-git-branch [] {
    # ... code to list git branches
}
  1. Add export module git.nu to mod.nu
  2. Add the parent of the completions directory to your NU_LIB_DIRS inside env.nu
$env.NU_LIB_DIRS = [
    ...
    $nu.default-config-dir
]
  1. import the completions to Nushell in your config.nuuse completions * Now you've set up a directory where you can put your completion files and you should have some Git completions the next time you start Nushell

Note This will use the file name (in our example git from git.nu) as the module name. This means some completions might not work if the definition has the base command in it's name. For example, if you defined our known externals in our git.nu as export extern 'git push' [], etc. and followed the rest of the steps, you would get subcommands like git git push, etc. You would need to call use completions git * to get the desired subcommands. For this reason, using main as outlined in the step above is the preferred way to define subcommands.

Setting environment + aliases (conda style)

def --env commands, export-env block and aliases can be used to dynamically manipulate "virtual environments" (a concept well known from Python).

We use it in our official virtualenv integration https://github.com/pypa/virtualenv/blob/main/src/virtualenv/activation/nushell/activate.nu

Another example could be our unofficial Conda module: https://github.com/nushell/nu_scripts/blob/f86a060c10f132407694e9ba0f536bfe3ee51efc/modules/virtual_environments/conda.nu

Warning Work In Progress

Hiding

Any custom command or alias, imported from a module or not, can be "hidden", restoring the previous definition. We do this with the hide command:

> def foo [] { "foo" }

> foo
foo

> hide foo

> foo  # error! command not found!

The hide command also accepts import patterns, just like use. The import pattern is interpreted slightly differently, though. It can be one of the following:

hide foo or hide greetings

  • If the name is a custom command or an environment variable, hides it directly. Otherwise:
  • If the name is a module name, hides all of its exports prefixed with the module name

hide greetings hello

  • Hides only the prefixed command / environment variable

hide greetings [hello, hi]

  • Hides only the prefixed commands / environment variables

hide greetings *

  • Hides all of the module's exports, without the prefix

Notehide is not a supported keyword at the root of a module (unlike def etc.)

Contributors: Jakub Žádník, Antoine Stevan, Justin Ma, Arnau Siches, David Matos, Fabian Achammer, Hofer-Julian, JT, JT, Jarrod Urban, Mikko Viitamäki, Máté FARKAS, Reilly Wood, Simon Stiefel, Traister101, Yethal, Zhora Trush