Job Control in Nushell

The Job Control chapter of Effective Shell covers task control in Bash. This post shows how the same ideas work in Nushell.

Job Control

For what job control is, see Effective Shell/Job-Control#What-is-job-control.

In short, it is the ability to move tasks between the foreground and the background.

Problems It Solves

  1. Freeing the terminal: move a long-running task into the background so the command line doesn’t get stuck, letting you multitask in a single window.
  2. Suspend and resume: pause the current program (e.g. Vim) to handle something urgent, then seamlessly return to where you were.
    P.S: if you use Tmux/Zellij, a floating pane also works for handling interruptions, see my post on that
  3. Task management: list every task, know exactly what is running, and kill any of them at will.

Job Management Overview

for UNIX Shell Users

  • Append & to a command to run it in the background
  • Use jobs to list background commands
  • Press Ctrl-Z to suspend a command and send it to the background
  • Use fg to bring a background command to the foreground

Nushell’s built-in job command provides functionality similar to UNIX shells. Run help job to see how it works.

You could say Nushell and PowerShell commands are quite self-explanatory. UNIX shells, by contrast, lean toward brevity, relying heavily on symbols and abbreviations that you have to memorize, which makes them hard for beginners to decipher.

$ help jobVarious commands for working with background jobs.You must use one of the following subcommands. Using this command as-is will only produce this help message.Usage:  > jobSubcommands:  job flush - Clear this job's mailbox.  job id - Get id of current job.  job kill - Kill a background job.  job list - List background jobs.  job recv - Read a message from the mailbox.  job send - Send a message to the mailbox of a job.  job spawn - Spawn a background job and retrieve its ID.  job tag - Add a description tag to a background job.  job unfreeze - Unfreeze a frozen process job in foreground.Flags:  -h, --help: Display the help message for this commandInput/output types:  ╭───┬─────────┬────────╮   #   input   output   ├───┼─────────┼────────┤   0  nothing  string   ╰───┴─────────┴────────╯

End of post, below I’ll pick a few subcommands I find interesting.

Spawn Background Jobs🫃🏻

Spawn means to hatch; in Nushell it means starting a background job.

  • P.S. I first ran into this word while modding Cyberpunk, where spawning NPCs uses exactly this word (😡why aren’t this NPC’s clothes loading)

Usage needs little explanation, so straight to an example. hexo clean; hexo gen takes ~10s and blocks the prompt while it runs, so it’s more convenient to push it into the background:

# Start a background job$ job spawn { hexo clean; hexo gen }# Confirm it is quietly working in the background$ job list╭───┬────┬────────┬──────────────╮ #  id   type       pids     ├───┼────┼────────┼──────────────┤ 0  34  thread  ╭───┬──────╮                  0  5359                  ╰───┴──────╯ ╰───┴────┴────────┴──────────────╯

Care about the job’s output? I haven’t found a corresponding Nushell API yet; one workaround is to redirect stdout/stderr to files:
hexo generate o> hexo.log e> hexo.err

Process Bound

When you run job spawn { ... }, Nushell actually starts a new Rust thread inside the current shell process to run the closure[1], which means:

  • Jobs in different Nushell processes are independent of each other; you can’t manage them across processes
  • When you exit Nushell, background jobs get cleaned up too:
$ exitThere are still background jobs running (1).Running `exit` a second time will kill all of them.
BUG? Orphan Process Warning

Although Nushell tries to clean up jobs, I found that with something like job spawn {hexo server}, which launches external child processes (node), the cleanup isn’t thorough. The node process doesn’t die; it becomes an orphan process. I filed an issue on GitHub: job spawn terminates direct child but leaves grandchild processes orphaned · Issue #17378 · nushell/nushell

Suppose:

  • Parent: the Nushell process has PID 53822
  • Child: the bun process started by hexo server has PID 64759
  • Grandchild: the node process started by bun has PID 64762

Run ps before and after exiting Nushell to check their state. You can see that after Nushell exits, the bun process is killed, but the node process’s PPID becomes 1 (the init process), meaning it was never cleaned up

Usage Scenarios

  • Uploading files
    • A concrete example: Atuin’s Nushell script uses job spawn to create an async upload task [2]
  • Downloading files
  • Compile Code etc.

Freeze 🥶

Just like in a UNIX shell, you can freeze a running foreground process:

  1. Run hx ./test.js in the foreground
  2. Press Ctrl+Z or send the process a SIGTSTP signal; Nushell reports: Job 20 is frozen
  3. Do something else…
  4. Use job unfreeze 20 to bring it back to the foreground and resume

A frozen job no longer consumes CPU, but it still occupies memory and keeps its file handles open, waiting for your next instruction.

Bash / ZshNushell
Resume in foregroundfg %1job unfreeze 20
Kill the jobkill %1job kill 20
Unfreeze into backgroundbg %1Issue #15196

  1. An anonymous function, often called a lambda function, which accepts parameters and closes over (i.e., uses) variables from outside its scope Closure | Nushell ↩︎

  2. Atuin is a popular shell tool that persists shell history commands into a SQLite database and can sync them to a server, giving you cross-shell, cross-device history sync ↩︎