Skip to main content

创建一个进程 (Bun.spawn())

提供一个字符串数组作为命令。Bun.spawn() 返回一个 Bun.Subprocess 对象。
const proc = Bun.spawn(["bun", "--version"]);
console.log(await proc.exited); // 0
Bun.spawn 的第二个参数是一个参数对象,用来配置子进程。
const proc = Bun.spawn(["bun", "--version"], {
  cwd: "./path/to/subdir", // 指定工作目录
  env: { ...process.env, FOO: "bar" }, // 指定环境变量
  onExit(proc, exitCode, signalCode, error) {
    // 退出处理器
  },
});

proc.pid; // 子进程的进程ID

输入流

默认情况下,子进程的输入流是 undefined;可以通过 stdin 参数进行配置。
const proc = Bun.spawn(["cat"], {
  stdin: await fetch("https://raw.githubusercontent.com/oven-sh/bun/main/examples/hashing.js"),
});

const text = await proc.stdout.text();
console.log(text); // "const input = "hello world".repeat(400); ..."
描述
null默认。 不向子进程提供输入
"pipe"返回一个 FileSink 用于快速增量写入
"inherit"继承父进程的 stdin
Bun.file()从指定文件读取
TypedArray | DataView使用二进制缓冲区作为输入
Response使用响应体 body 作为输入
Request使用请求体 body 作为输入
ReadableStream使用可读流作为输入
Blob使用 Blob 作为输入
number从给定文件描述符的文件读取
"pipe" 选项允许从父进程向子进程的输入流进行增量写入。
const proc = Bun.spawn(["cat"], {
  stdin: "pipe", // 返回可写的 FileSink
});

// 入队字符串数据
proc.stdin.write("hello");

// 入队二进制数据
const enc = new TextEncoder();
proc.stdin.write(enc.encode(" world!"));

// 发送缓冲数据
proc.stdin.flush();

// 关闭输入流
proc.stdin.end();
将一个 ReadableStream 传给 stdin,可以让你直接从 JavaScript 的 ReadableStream 向子进程输入流传输数据:
const stream = new ReadableStream({
  start(controller) {
    controller.enqueue("Hello from ");
    controller.enqueue("ReadableStream!");
    controller.close();
  },
});

const proc = Bun.spawn(["cat"], {
  stdin: stream,
  stdout: "pipe",
});

const output = await proc.stdout.text();
console.log(output); // "Hello from ReadableStream!"

输出流

你可以通过 stdoutstderr 属性读取子进程的输出。默认情况下它们是 ReadableStream 的实例。
const proc = Bun.spawn(["bun", "--version"]);
const text = await proc.stdout.text();
console.log(text); // => "1.3.3\n"
通过向 stdout/stderr 传入以下值之一来配置输出流:
描述
"pipe"stdout 的默认值。 将输出导入返回的 Subprocess 对象的 ReadableStream
"inherit"stderr 的默认值。 继承自父进程
"ignore"丢弃输出
Bun.file()写入指定文件
number写入指定文件描述符的文件

退出处理

使用 onExit 回调监听进程退出或被杀死。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const proc = Bun.spawn(["bun", "--version"], {
  onExit(proc, exitCode, signalCode, error) {
    // 退出处理器
  },
});
为方便起见,exited 属性是一个在进程退出时解析的 Promise
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const proc = Bun.spawn(["bun", "--version"]);

await proc.exited; // 进程退出时解析
proc.killed; // 布尔值 — 进程是否被杀死?
proc.exitCode; // null | 数字
proc.signalCode; // null | "SIGABRT" | "SIGALRM" | ...
杀死进程:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const proc = Bun.spawn(["bun", "--version"]);
proc.kill();
proc.killed; // true

proc.kill(15); // 指定信号码
proc.kill("SIGTERM"); // 指定信号名
父进程 bun 在所有子进程退出前不会终止。使用 proc.unref() 可以将子进程与父进程分离。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const proc = Bun.spawn(["bun", "--version"]);
proc.unref();

资源使用

进程退出后,你可以获取该进程的资源使用信息:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited;

const usage = proc.resourceUsage();
console.log(`最大内存使用量: ${usage.maxRSS} 字节`);
console.log(`CPU 时间(用户态): ${usage.cpuTime.user} 微秒`);
console.log(`CPU 时间(内核态): ${usage.cpuTime.system} 微秒`);

使用 AbortSignal

你可以使用 AbortSignal 中止子进程:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
const controller = new AbortController();
const { signal } = controller;

const proc = Bun.spawn({
  cmd: ["sleep", "100"],
  signal,
});

// 后续中止进程:
controller.abort();

使用 timeout 和 killSignal

你可以为子进程设置超时时间,超时后自动终止进程:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
// 5秒后杀死进程
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000, // 5秒,单位毫秒
});

await proc.exited; // 5秒后解析
默认情况下,超时进程会使用 SIGTERM 信号被杀死。你可以通过 killSignal 选项指定不同信号:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
// 5秒后使用 SIGKILL 杀死进程
const proc = Bun.spawn({
  cmd: ["sleep", "10"],
  timeout: 5000,
  killSignal: "SIGKILL", // 可为信号名称或信号编号
});
killSignal 选项也控制在 AbortSignal 触发时发送的信号。

使用 maxBuffer

对于 spawnSync,你可以限制输出的最大字节数,超过则杀死进程:
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79index.ts
// ‘yes’ 进程输出超过100字节后被杀死
const result = Bun.spawnSync({
  cmd: ["yes"], // Windows 下使用 ["bun", "exec", "yes"]
  maxBuffer: 100,
});
// 进程退出

进程间通信 (IPC)

Bun 支持在两个 bun 进程间建立直接的进程间通信通道。要从被创建的 Bun 子进程接收消息,请指定 ipc 处理器。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79parent.ts
const child = Bun.spawn(["bun", "child.ts"], {
  ipc(message) {
    /**
     * 从子进程收到的消息
     **/
  },
});
父进程可通过返回的 Subprocess 对象上的 .send() 方法向子进程发送消息。子进程在 ipc 处理器第二个参数中获得发送者引用。
https://mintcdn.com/ikxin/RzFFGbzo0-4huILA/icons/typescript.svg?fit=max&auto=format&n=RzFFGbzo0-4huILA&q=85&s=a3dffd2241f05776d3bd25171d0c5a79parent.ts
const childProc = Bun.spawn(["bun", "child.ts"], {
  ipc(message, childProc) {
    /**
     * 从子进程收到的消息
     **/
    childProc.send("Respond to child");
  },
});

childProc.send("I am your father"); // 父进程也可向子进程发送消息
子进程可用 process.send() 向父进程发送消息,用 process.on("message") 接收消息。这与 Node.js 中 child_process.fork() 使用的 API 相同。
child.ts
process.send("Hello from child as string");
process.send({ message: "Hello from child as object" });

process.on("message", message => {
  // 打印来自父进程的消息
  console.log(message);
});
child.ts
// 发送字符串消息
process.send("Hello from child as string");

// 发送对象消息
process.send({ message: "Hello from child as object" });
serialization 选项控制两进程间通信的序列化格式:
  • advanced:(默认)消息使用 JSC 的 serialize API 序列化,支持克隆所有 structuredClone 支持的类型 ,但不支持对象所有权转移。
  • json:消息使用 JSON.stringifyJSON.parse 序列化,支持对象类型比 advanced 少。
断开与父进程的 IPC 通道可调用:
childProc.disconnect();

Bun 与 Node.js 之间的 IPC

要在 bun 和 Node.js 进程间使用 IPC,请在 Bun.spawn 设置 serialization: "json"。这是因为 Node.js 和 Bun 使用不同的 JS 引擎及不同的对象序列化格式。
bun-node-ipc.js
if (typeof Bun !== "undefined") {
  const prefix = `[bun ${process.versions.bun} 🐇]`;
  const node = Bun.spawn({
    cmd: ["node", __filename],
    ipc({ message }) {
      console.log(message);
      node.send({ message: `${prefix} 👋 hey node` });
      node.kill();
    },
    stdio: ["inherit", "inherit", "inherit"],
    serialization: "json",
  });

  node.send({ message: `${prefix} 👋 hey node` });
} else {
  const prefix = `[node ${process.version}]`;
  process.on("message", ({ message }) => {
    console.log(message);
    process.send({ message: `${prefix} 👋 hey bun` });
  });
}

终端(PTY)支持

对于交互式终端应用,可以使用 terminal 选项为子进程附加伪终端(PTY)。这让子进程认为自己运行在真实终端中,启用彩色输出、光标移动和交互式提示等功能。
const proc = Bun.spawn(["bash"], {
  terminal: {
    cols: 80,
    rows: 24,
    data(terminal, data) {
      // 终端接收数据时被调用
      process.stdout.write(data);
    },
  },
});

// 向终端写数据
proc.terminal.write("echo hello\n");

// 等待进程退出
await proc.exited;

// 关闭终端
proc.terminal.close();
提供 terminal 选项时:
  • 子进程中 process.stdout.isTTYtrue
  • stdinstdoutstderr 都绑定到终端
  • proc.stdinproc.stdoutproc.stderr 返回 null —— 使用终端对象代替
  • 可通过 proc.terminal 访问终端

终端选项

选项描述默认值
cols列数80
rows行数24
namePTY 配置的终端类型(通过 env 选项单独设置 TERM 环境变量)"xterm-256color"
data接收数据时回调 (terminal, data) => void
exitPTY 流关闭时回调(EOF 或错误)。exitCode 是 PTY 生命周期状态(0为EOF,1为错误),而非子进程退出码。使用 proc.exited 监听进程退出。
drain可写流准备好接收更多数据时回调 (terminal) => void

终端方法

proc.terminal 返回的 Terminal 对象具有以下方法:
// 向终端写入数据
proc.terminal.write("echo hello\n");

// 调整终端大小
proc.terminal.resize(120, 40);

// 设置原生模式(禁用行缓冲与回显)
proc.terminal.setRawMode(true);

// 保持事件循环活动直到终端关闭
proc.terminal.ref();
proc.terminal.unref();

// 关闭终端
proc.terminal.close();

可复用终端

你可以独立创建一个终端对象并在多个子进程间重用:
await using terminal = new Bun.Terminal({
  cols: 80,
  rows: 24,
  data(term, data) {
    process.stdout.write(data);
  },
});

// 创建第一个进程
const proc1 = Bun.spawn(["echo", "first"], { terminal });
await proc1.exited;

// 复用终端启动另一个进程
const proc2 = Bun.spawn(["echo", "second"], { terminal });
await proc2.exited;

// 终端会被 `await using` 自动关闭
传入已有的 Terminal 对象时:
  • 终端可跨多个进程复用
  • 你负责何时关闭终端
  • exit 回调在调用 terminal.close() 时触发,而非每个子进程退出时
  • 使用 proc.exited 侦测每个子进程退出
这适合在同一终端会话中顺序运行多个命令。
终端支持仅限 POSIX 系统(Linux、macOS)。Windows 不支持此功能。

阻塞式 API (Bun.spawnSync())

Bun 提供了 Bun.spawn 的同步版本 Bun.spawnSync。这是一个阻塞 API,支持与 Bun.spawn 相同的输入和参数。它返回一个 SyncSubprocess 对象,与 Subprocess 有几点不同:
  1. 含有表示进程是否以零退出码退出的 success 属性。
  2. stdoutstderrBuffer 实例,而非 ReadableStream
  3. 没有 stdin 属性。若需增量写入输入流,请使用 Bun.spawn
const proc = Bun.spawnSync(["echo", "hello"]);

console.log(proc.stdout.toString());
// => "hello\n"
原则上,异步的 Bun.spawn 适合 HTTP 服务器和应用,Bun.spawnSync 更适合构建命令行工具。

性能基准

⚡️ 底层,Bun.spawnBun.spawnSync 使用 posix_spawn(3) 系统调用。
Bun 的 spawnSync 比 Node.js 的 child_process 模块快 60%。
terminal
bun spawn.mjs
cpu: Apple M1 Max
runtime: bun 1.x (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi  888.14 µs/iter    (821.83 µs … 1.2 ms) 905.92 µs      1 ms   1.03 ms
terminal
node spawn.node.mjs
cpu: Apple M1 Max
runtime: node v18.9.1 (arm64-darwin)

benchmark              time (avg)             (min … max)       p75       p99      p995
--------------------------------------------------------- -----------------------------
spawnSync echo hi    1.47 ms/iter     (1.14 ms … 2.64 ms)   1.57 ms   2.37 ms   2.52 ms

参考

下面是 Spawn API 及类型的参考。真实的类型中包含复杂泛型以根据 Bun.spawnBun.spawnSync 传入的选项严密类型化 Subprocess 流。完整细节参见 bun.d.ts 中的定义。
See Typescript Definitions
interface Bun {
  spawn(command: string[], options?: SpawnOptions.OptionsObject): Subprocess;
  spawnSync(command: string[], options?: SpawnOptions.OptionsObject): SyncSubprocess;

  spawn(options: { cmd: string[] } & SpawnOptions.OptionsObject): Subprocess;
  spawnSync(options: { cmd: string[] } & SpawnOptions.OptionsObject): SyncSubprocess;
}

namespace SpawnOptions {
  interface OptionsObject {
    cwd?: string;
    env?: Record<string, string | undefined>;
    stdio?: [Writable, Readable, Readable];
    stdin?: Writable;
    stdout?: Readable;
    stderr?: Readable;
    onExit?(
      subprocess: Subprocess,
      exitCode: number | null,
      signalCode: number | null,
      error?: ErrorLike,
    ): void | Promise<void>;
    ipc?(message: any, subprocess: Subprocess): void;
    serialization?: "json" | "advanced";
    windowsHide?: boolean;
    windowsVerbatimArguments?: boolean;
    argv0?: string;
    signal?: AbortSignal;
    timeout?: number;
    killSignal?: string | number;
    maxBuffer?: number;
    terminal?: TerminalOptions; // 仅限 POSIX 的 PTY 支持
  }

  type Readable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // 等价于 "ignore"
    | undefined // 使用默认值
    | BunFile
    | ArrayBufferView
    | number;

  type Writable =
    | "pipe"
    | "inherit"
    | "ignore"
    | null // 等价于 "ignore"
    | undefined // 使用默认值
    | BunFile
    | ArrayBufferView
    | number
    | ReadableStream
    | Blob
    | Response
    | Request;
}

interface Subprocess extends AsyncDisposable {
  readonly stdin: FileSink | number | undefined | null;
  readonly stdout: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
  readonly stderr: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
  readonly readable: ReadableStream<Uint8Array<ArrayBuffer>> | number | undefined | null;
  readonly terminal: Terminal | undefined;
  readonly pid: number;
  readonly exited: Promise<number>;
  readonly exitCode: number | null;
  readonly signalCode: NodeJS.Signals | null;
  readonly killed: boolean;

  kill(exitCode?: number | NodeJS.Signals): void;
  ref(): void;
  unref(): void;

  send(message: any): void;
  disconnect(): void;
  resourceUsage(): ResourceUsage | undefined;
}

interface SyncSubprocess {
  stdout: Buffer | undefined;
  stderr: Buffer | undefined;
  exitCode: number;
  success: boolean;
  resourceUsage: ResourceUsage;
  signalCode?: string;
  exitedDueToTimeout?: true;
  pid: number;
}

interface TerminalOptions {
  cols?: number;
  rows?: number;
  name?: string;
  data?: (terminal: Terminal, data: Uint8Array<ArrayBuffer>) => void;
  /** PTY 流关闭时调用(EOF 或错误)。exitCode 是 PTY 生命周期状态(0=EOF,1=错误),非子进程退出码。 */
  exit?: (terminal: Terminal, exitCode: number, signal: string | null) => void;
  drain?: (terminal: Terminal) => void;
}

interface Terminal extends AsyncDisposable {
  readonly stdin: number;
  readonly stdout: number;
  readonly closed: boolean;
  write(data: string | BufferSource): number;
  resize(cols: number, rows: number): void;
  setRawMode(enabled: boolean): void;
  ref(): void;
  unref(): void;
  close(): void;
}

interface ResourceUsage {
  contextSwitches: {
    voluntary: number;
    involuntary: number;
  };

  cpuTime: {
    user: number;
    system: number;
    total: number;
  };
  maxRSS: number;

  messages: {
    sent: number;
    received: number;
  };
  ops: {
    in: number;
    out: number;
  };
  shmSize: number;
  signalCount: number;
  swapCount: number;
}

type Signal =
  | "SIGABRT"
  | "SIGALRM"
  | "SIGBUS"
  | "SIGCHLD"
  | "SIGCONT"
  | "SIGFPE"
  | "SIGHUP"
  | "SIGILL"
  | "SIGINT"
  | "SIGIO"
  | "SIGIOT"
  | "SIGKILL"
  | "SIGPIPE"
  | "SIGPOLL"
  | "SIGPROF"
  | "SIGPWR"
  | "SIGQUIT"
  | "SIGSEGV"
  | "SIGSTKFLT"
  | "SIGSTOP"
  | "SIGSYS"
  | "SIGTERM"
  | "SIGTRAP"
  | "SIGTSTP"
  | "SIGTTIN"
  | "SIGTTOU"
  | "SIGUNUSED"
  | "SIGURG"
  | "SIGUSR1"
  | "SIGUSR2"
  | "SIGVTALRM"
  | "SIGWINCH"
  | "SIGXCPU"
  | "SIGXFSZ"
  | "SIGBREAK"
  | "SIGLOST"
  | "SIGINFO";