﻿---
title: Job Control in Nushell
date: 2026-01-19
tags:
  - Shell
  - Nushell
  - Asynchronous
  - Workflow
excerpt: "Effective Shell 书中的 Job Control 章节，介绍了如何在 Bash 中进行任务控制。感觉是比较有用的。本文将介绍其在 Nushell 中的用法。"
---

## Job Control

什么是Job Control，可以参考 [Effective Shell/Job-Control#What-is-job-control](https://effective-shell.com/part-2-core-skills/job-control#what-is-job-control)。

概括来讲，就是允许用户**在前台和后台之间切换任务的能力**

### 解决的问题

1. **释放终端**：将耗时任务转入后台运行，避免命令行被卡住，实现单窗口下的多任务并行。
2. **挂起恢复**：暂停当前程序（e.g. Vim）去处理突发事务，然后能无缝回到之前的工作状态。
   P.S: 如果使用Tmux/Zellij，也可以尝试使用floating pane来处理突发事务，参考我的[[tmux_floating_pane|这篇文章]]
3. **任务管理**：列出所有任务清单，清楚掌握谁在运行，并能随时关闭指定任务。

## Job Management Overview

> [!TIP]- for UNIX Shell Users
>
> - 在命令末尾添加 `&` 来在后台运行命令
> - 使用 `{bash} jobs` 列出后台运行的命令
> - 按 `Ctrl-Z` 将命令**暂停**并转为后台
> - 使用 `fg` 将后台运行的命令转入前台

Nushell built-in的`{nu} job`命令，提供了类似UNIX Shell的功能。使用`{nu} help job`可以查看其用法。

可以说Nushell和Powershell的命令十分语义化了。而UNIX Shell，设计偏简洁，会大量使用符号和缩写，依赖记忆，初学者难以理解其含义

```nu
$ help job
Various 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:
  > job

Subcommands:
  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 command

Input/output types:
  ╭───┬─────────┬────────╮
  │ # │  input  │ output │
  ├───┼─────────┼────────┤
  │ 0 │ nothing │ string │
  ╰───┴─────────┴────────╯
```

~~本文结束~~, 下面简单挑几个我感兴趣的子命令来介绍。

## Spawn Background Jobs🫃🏻

**Spawn** <button class="physical-btn" onclick="new Audio('https://cn.bing.com//dict/mediamp3?blob=audio%2Ftom%2F11%2Fef%2F11EFB3AC01ED4D120081B49EC6A3C959.mp3').play()"> [spɔːn]🔊 </button>，意为**孵化**，在 Nushell 中表示**启动一个后台任务**。

* P.S. 第一次接触这个单词，是给Cyberpunk打Mod，生成NPC用的就是Spawn这个单词 (😡你这NPC衣服怎么没加载啊)

使用方法无需过多赘述，直接看例子，`{nu} hexo clean; hexo gen` 命令执行需要~10s，期间会阻塞交互，将其放在后台执行会方便些

```nu
# 启动后台任务
$ job spawn { hexo clean; hexo gen }

# 确认它正在后台默默工作
$ job list
╭───┬────┬────────┬──────────────╮
│ # │ id │  type  │     pids     │
├───┼────┼────────┼──────────────┤
│ 0 │ 34 │ thread │ ╭───┬──────╮ │
│   │    │        │ │ 0 │ 5359 │ │
│   │    │        │ ╰───┴──────╯ │
╰───┴────┴────────┴──────────────╯
```

关心任务的输出？暂时没找到Nushell对应的API，一个workaround是把stdout/stderr重定向到文件中:
`{nu} hexo generate o> hexo.log e> hexo.err`

### Process Bound

当运行 `{nu} job spawn { ... }` 时，Nushell 实际上是在当前的 Shell 进程内部启动了一个新的**Rust Thread**来运行这个Closure[^1]，也就是说:

- 多个Nushell进程之间的jobs是**相互独立**的，无法跨进程管理
- 当前当你Exit Nushell后，后台任务也会被清理：

```nu
$ exit
There are still background jobs running (1).
Running `exit` a second time will kill all of them.
```

> [!ERROR] BUG? Orphan Process Warning
>
> 虽然 Nushell 会尝试清理任务，但发现像 `{nu} job spawn {hexo server}` 这种启动了**外部子进程**（`node`）清理得并不彻底，node进程不会死，而是变成了**孤儿进程**（Orphan Process）。在Github上提了个Issue [job spawn terminates direct child but leaves grandchild processes orphaned · Issue #17378 · nushell/nushell](https://github.com/nushell/nushell/issues/17378)
>
> 假设:
> - Parent: Nushell进程的PID是53822，
> - Child: `{nu} hexo server`启动的bun进程PID是64759
> - Grandchild: bun启动的node进程PID是64762
>
> 分别在exit nushell前后运行`{nu} ps`，查看它们的状态; 可以看到，Nushell退出后，bun进程被杀死了，但node进程的PPID变成了1（init进程），说明它并没有被清理掉

### Usage Scenarios

- Uploading files
  - 一个具体的例子，Atuin的Nushell的脚本中，就使用了 `{nu} job spawn` 来创建上传的异步任务 [^2]
- Downloading files
- Compile Code etc.

## Freeze 🥶

就像在 UNIX Shell 中一样，你可以**Freeze**正在运行的前台进程:

1. 在前台运行 `{nu} hx ./test.js`
2. 按 <kbd>Ctrl+Z</kbd> 或给进程发送 `SIGTSTP`信号; Nushell 报告：`{nu} Job 20 is frozen`
3. Do something else...
4. 使用 `{nu} job unfreeze 20` 可以将其恢复到前台继续运行

<video autoplay loop muted playsinline>
  <source src="https://assets.vluv.space/job_control.webm" type="video/webm">
</video>

被冻结的任务不再消耗 CPU 资源，但仍然占用内存，并且保持着打开的文件句柄，等待用户下一步指令

|                    | Bash / Zsh       | Nushell                                                           |
| :----------------- | :--------------- | :---------------------------------------------------------------- |
| **恢复到前台**     | `{bash} fg %1`   | `{nu} job unfreeze 20`                                            |
| **杀死任务**       | `{bash} kill %1` | `{nu} job kill 20`                                                |
| **解冻并转后台跑** | `{bash} bg %1`   | ❌ [Issue #15196](https://github.com/nushell/nushell/issues/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](https://www.nushell.sh/lang-guide/chapters/types/basic_types/closure.html)
[^2]: [Atuin](https://atuin.sh/) 是一款流行的Shell工具，它将Shell History Command持久化到SQLite数据库中，并支持将其同步到服务器，实现跨Shell+跨设备的历史命令同步
