> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# Shell

> 使用 Bun 的 shell 脚本 API 从 JavaScript 运行 shell 命令

Bun Shell 让使用 JavaScript 和 TypeScript 编写 shell 脚本变得有趣。它是一个跨平台的类 bash shell，并支持与 JavaScript 的互操作。

快速入门：

```ts index.ts icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { $ } from "bun";

const response = await fetch("https://example.com");

// 使用 Response 作为 stdin。
await $`cat < ${response} | wc -c`; // 1256
```

***

## 特性

* **跨平台**：可在 Windows、Linux 和 macOS 上运行。无需安装 `rimraf` 或 `cross-env`，您可以使用 Bun Shell。常见 shell 命令如 `ls`、`cd` 和 `rm` 都已原生实现。
* **熟悉**：Bun Shell 是一个类 bash shell，支持重定向、管道和环境变量。
* **Glob 模式**：原生支持 glob 模式，包括 `**`、`*` 和 `{expansion}`。
* **模板字面量**：模板字面量执行 shell 命令并插值变量和表达式。
* **安全性**：Bun Shell 默认转义所有字符串，防止 shell 注入攻击。
* **JavaScript 互操作**：使用 `Response`、`ArrayBuffer`、`Blob`、`Bun.file(path)` 和其他 JavaScript 对象作为 stdin、stdout 和 stderr。
* **Shell 脚本**：Bun Shell 运行 shell 脚本（`.bun.sh` 文件）。
* **自定义解释器**：Bun Shell 是一个小型编程语言，拥有自己的词法分析器、解析器和解释器，使用 Rust 编写。

***

## 快速入门

最简单的 shell 命令是 `echo`。要运行它，请使用 `$` 模板字面量标签：

```js theme={null}
import { $ } from "bun";

await $`echo "Hello World!"`; // Hello World!
```

默认情况下，shell 命令会打印到 stdout。要静默输出，请调用 `.quiet()`：

```js theme={null}
import { $ } from "bun";

await $`echo "Hello World!"`.quiet(); // 无输出
```

要将命令输出作为文本读取，请使用 `.text()`：

```js theme={null}
import { $ } from "bun";

// .text() 会自动为您调用 .quiet()
const welcome = await $`echo "Hello World!"`.text();

console.log(welcome); // Hello World!\n
```

默认情况下，`await` 会返回 stdout 和 stderr 作为 `Buffer`。

```js theme={null}
import { $ } from "bun";

const { stdout, stderr } = await $`echo "Hello!"`.quiet();

console.log(stdout); // Buffer(7) [ 72, 101, 108, 108, 111, 33, 10 ]
console.log(stderr); // Buffer(0) []
```

***

## 错误处理

默认情况下，非零退出代码会抛出错误。`ShellError` 包含所运行命令的信息。

```js theme={null}
import { $ } from "bun";

try {
  const output = await $`something-that-may-fail`.text();
  console.log(output);
} catch (err) {
  console.log(`Failed with code ${err.exitCode}`);
  console.log(err.stdout.toString());
  console.log(err.stderr.toString());
}
```

`.nothrow()` 禁用抛出。您可以自己检查结果的 `exitCode`。

```js theme={null}
import { $ } from "bun";

const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();

if (exitCode !== 0) {
  console.log(`Non-zero exit code ${exitCode}`);
}

console.log(stdout);
console.log(stderr);
```

要更改所有命令的默认行为，请在 `$` 函数本身上调用 `.nothrow()` 或 `.throws(boolean)`。

```js theme={null}
import { $ } from "bun";
// shell promises 不会抛出，这意味着您必须
// 在每个 shell 命令上手动检查 `exitCode`。
$.nothrow(); // 等同于 $.throws(false)

// 默认行为，非零退出代码会抛出错误
$.throws(true);

// $.nothrow() 的别名
$.throws(false);

await $`something-that-may-fail`; // 不会抛出异常
```

***

## 重定向

使用典型的 Bash 运算符重定向命令的**输入**或**输出**：

* `<` 重定向 stdin
* `>` 或 `1>` 重定向 stdout
* `2>` 重定向 stderr
* `&>` 重定向 stdout 和 stderr 两者
* `>>` 或 `1>>` 重定向 stdout，**追加**到目标，而不是覆盖
* `2>>` 重定向 stderr，**追加**到目标，而不是覆盖
* `&>>` 重定向 stdout 和 stderr 两者，**追加**到目标，而不是覆盖
* `1>&2` 将 stdout 重定向到 stderr（写入 stdout 的内容改为写入 stderr）
* `2>&1` 将 stderr 重定向到 stdout（写入 stderr 的内容改为写入 stdout）

Bun Shell 还支持从 JavaScript 对象重定向输入和输出。

### 示例：将输出重定向到 JavaScript 对象（`>`）

要将 stdout 重定向到 JavaScript 对象，请使用 `>` 运算符：

```js theme={null}
import { $ } from "bun";

const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;

console.log(buffer.toString()); // Hello World!\n
```

您可以将输出重定向到这些 JavaScript 对象：

* `Buffer`、`Uint8Array`、`Uint16Array`、`Uint32Array`、`Int8Array`、`Int16Array`、`Int32Array`、`Float32Array`、`Float64Array`、`ArrayBuffer`、`SharedArrayBuffer`（写入底层缓冲区）
* `Bun.file(path)`、`Bun.file(fd)`（写入文件）

### 示例：从 JavaScript 对象重定向输入（`<`）

要将 JavaScript 对象用作 stdin，请使用 `<` 运算符：

```js theme={null}
import { $ } from "bun";

const response = new Response("hello i am a response body");

const result = await $`cat < ${response}`.text();

console.log(result); // hello i am a response body
```

您可以从这些 JavaScript 对象重定向输入：

* `Buffer`、`Uint8Array`、`Uint16Array`、`Uint32Array`、`Int8Array`、`Int16Array`、`Int32Array`、`Float32Array`、`Float64Array`、`ArrayBuffer`、`SharedArrayBuffer`（从底层缓冲区读取）
* `Bun.file(path)`、`Bun.file(fd)`（从文件读取）
* `Response`（从 body 读取）

### 示例：重定向 stdin -> 文件

```js theme={null}
import { $ } from "bun";

await $`cat < myfile.txt`;
```

### 示例：重定向 stdout -> 文件

```js theme={null}
import { $ } from "bun";

await $`echo bun! > greeting.txt`;
```

### 示例：重定向 stderr -> 文件

```js theme={null}
import { $ } from "bun";

await $`bun run index.ts 2> errors.txt`;
```

### 示例：重定向 stderr -> stdout

```js theme={null}
import { $ } from "bun";

// 将 stderr 重定向到 stdout，因此所有输出
// 都会在 stdout 上可用
await $`bun run ./index.ts 2>&1`;
```

### 示例：重定向 stdout -> stderr

```js theme={null}
import { $ } from "bun";

// 将 stdout 重定向到 stderr，因此所有输出
// 都会在 stderr 上可用
await $`bun run ./index.ts 1>&2`;
```

## 管道（`|`）

与 bash 类似，您可以将一个命令的输出通过管道传递给另一个命令：

```js theme={null}
import { $ } from "bun";

const result = await $`echo "Hello World!" | wc -w`.text();

console.log(result); // 2\n
```

您也可以与 JavaScript 对象配合使用管道：

```js theme={null}
import { $ } from "bun";

const response = new Response("hello i am a response body");

const result = await $`cat < ${response} | wc -w`.text();

console.log(result); // 6\n
```

## 命令替换（`$(...)`）

命令替换将另一个命令的输出插入到当前脚本中：

```js theme={null}
import { $ } from "bun";

// 打印当前提交的哈希值
await $`echo Hash of current commit: $(git rev-parse HEAD)`;
```

输出作为文本插入，因此您可以使用它来声明 shell 变量：

```js theme={null}
import { $ } from "bun";

await $`
  REV=$(git rev-parse HEAD)
  docker built -t myapp:$REV
  echo Done building docker image "myapp:$REV"
`;
```

<Note>
  由于 Bun 内部使用输入模板字面量的特殊 [`raw`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Template_literals#raw_strings) 属性，因此使用反引号语法进行命令替换将不起作用：

  ```ts icon="file-code" theme={null}
  import { $ } from "bun";

  await $`echo \`echo hi\``;
  ```

  它不会打印：

  ```
  hi
  ```

  而是打印：

  ```
  `echo hi`
  ```

  请改用 `$(...)` 语法。
</Note>

***

## 环境变量

像 bash 一样设置环境变量：

```js theme={null}
import { $ } from "bun";

await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\n
```

使用字符串插值来设置值：

```js theme={null}
import { $ } from "bun";

const foo = "bar123";

await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\n
```

输入默认被转义，防止 shell 注入攻击：

```js theme={null}
import { $ } from "bun";

const foo = "bar123; rm -rf /tmp";

await $`FOO=${foo} bun -e 'console.log(process.env.FOO)'`; // bar123; rm -rf /tmp\n
```

### 更改环境变量

默认情况下，所有命令都使用 `process.env` 作为其环境变量。

要更改单个命令的环境变量，请调用 `.env()`：

```js theme={null}
import { $ } from "bun";

await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // bar
```

要更改所有命令的默认环境变量，请调用 `$.env`：

```js theme={null}
import { $ } from "bun";

$.env({ FOO: "bar" });

// 全局设置的 $FOO
await $`echo $FOO`; // bar

// 局部设置的 $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz
```

要将环境变量重置为默认值，请调用不带参数的 `$.env()`：

```js theme={null}
import { $ } from "bun";

$.env({ FOO: "bar" });

// 全局设置的 $FOO
await $`echo $FOO`; // bar

// 局部设置的 $FOO
await $`echo $FOO`.env(undefined); // ""
```

### 更改工作目录

要更改命令的工作目录，请将字符串传递给 `.cwd()`：

```js theme={null}
import { $ } from "bun";

await $`pwd`.cwd("/tmp"); // /tmp
```

要更改所有命令的默认工作目录，请调用 `$.cwd`：

```js theme={null}
import { $ } from "bun";

$.cwd("/tmp");

// 全局设置的工作目录
await $`pwd`; // /tmp

// 局部设置的工作目录
await $`pwd`.cwd("/"); // /
```

***

## 读取输出

要将命令的输出作为字符串读取，请使用 `.text()`：

```js theme={null}
import { $ } from "bun";

const result = await $`echo "Hello World!"`.text();

console.log(result); // Hello World!\n
```

### 将输出读取为 JSON

要将命令的输出作为 JSON 读取，请使用 `.json()`：

```js theme={null}
import { $ } from "bun";

const result = await $`echo '{"foo": "bar"}'`.json();

console.log(result); // { foo: "bar" }
```

### 逐行读取输出

要逐行读取命令的输出，请使用 `.lines()`：

```js theme={null}
import { $ } from "bun";

for await (let line of $`echo "Hello World!"`.lines()) {
  console.log(line); // Hello World!
}
```

您也可以在已完成的命令上使用 `.lines()`：

```js theme={null}
import { $ } from "bun";

const search = "bun";

for await (let line of $`cat list.txt | grep ${search}`.lines()) {
  console.log(line);
}
```

### 将输出读取为 Blob

要将命令的输出作为 Blob 读取，请使用 `.blob()`：

```js theme={null}
import { $ } from "bun";

const result = await $`echo "Hello World!"`.blob();

console.log(result); // Blob(13) { size: 13, type: "text/plain" }
```

***

## 内置命令

为跨平台兼容性，Bun Shell 实现了一组内置命令，此外还可以从 `PATH` 环境变量读取命令。

* `cd`：更改工作目录
* `ls`：列出目录中的文件（支持 `-l` 长列表格式）
* `rm`：删除文件和目录
* `echo`：打印文本
* `pwd`：打印工作目录
* `bun`：在 bun 中运行 bun
* `cat`
* `touch`
* `mkdir`
* `which`
* `mv`
* `exit`
* `true`
* `false`
* `yes`
* `seq`
* `dirname`
* `basename`

**部分**实现：

* `mv`：移动文件和目录（缺少跨设备支持）

**尚未**实现，但已计划：

* 查看 [Issue #9716](https://github.com/oven-sh/bun/issues/9716) 获取完整列表。

***

## 工具函数

Bun Shell 还实现了一组用于处理 shell 的工具函数。

### `$.braces`（花括号展开）

`$.braces` 实现了 shell 命令的[花括号展开](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html)：

```js theme={null}
import { $ } from "bun";

await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]
```

### `$.escape`（转义字符串）

将 Bun Shell 的转义逻辑暴露为函数：

```js theme={null}
import { $ } from "bun";

console.log($.escape('$(foo) `bar` "baz"'));
// => "\$(foo) \`bar\` \"baz\""
```

要跳过转义，请将字符串包装在 `{ raw: 'str' }` 对象中：

```js theme={null}
import { $ } from "bun";

await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => baz
```

***

## `.sh` 文件加载器

对于简单的 shell 脚本，您可以使用 Bun Shell 代替 `/bin/sh`。将带有 `.sh` 扩展名的文件传递给 `bun`：

```sh script.sh icon="file-code" theme={null}
echo "Hello World! pwd=$(pwd)"
```

```sh terminal icon="terminal" theme={null}
bun ./script.sh
```

```txt theme={null}
Hello World! pwd=/home/demo
```

Bun Shell 脚本是跨平台的，因此它们可以在 Windows 上运行：

```powershell powershell icon="windows" theme={null}
bun .\script.sh
```

```txt theme={null}
Hello World! pwd=C:\Users\Demo
```

***

## 实现说明

Bun Shell 是一个用 Rust 实现的小型编程语言，拥有手写的词法分析器、解析器和解释器。与 bash、zsh 和其他 shell 不同，Bun Shell 并发运行操作。

***

## Bun Shell 的安全机制

Bun Shell 有意**不调用系统 shell**（如 `/bin/sh`）。它是 bash 的重新实现，运行在同一个 Bun 进程中。

解析命令参数时，它会将所有**插值变量**视为单个字面量字符串。

这可以防止**命令注入**：

```js theme={null}
import { $ } from "bun";

const userInput = "my-file.txt; rm -rf /";

// 安全：`userInput` 被视为单个引用字符串
await $`ls ${userInput}`;
```

这里，`userInput` 被视为单个字符串，因此 `ls` 尝试读取名为 `my-file.txt; rm -rf /` 的单个目录的内容。

### 安全注意事项

虽然默认情况下防止了命令注入，但在某些场景下您仍需负责安全。

与 `Bun.spawn` 或 `node:child_process.exec()` API 类似，您可以有意执行一个启动新 shell 的命令（例如 `bash -c`）并附带参数。

当您这样做时，您移交了控制权，Bun 的内置保护不再适用于该新 shell 解释的字符串。

```js theme={null}
import { $ } from "bun";

const userInput = "world; touch /tmp/pwned";

// 不安全：您显式地用 `bash -c` 启动了一个新的 shell 进程。
// 这个新 shell 将执行 `touch` 命令。任何以这种方式传递的用户输入
// 必须严格清理。
await $`bash -c "echo ${userInput}"`;
```

### 参数注入

Bun Shell 无法知道外部命令如何解释其自身的命令行参数。攻击者可以提供目标程序识别为其自身选项或标志的输入，从而导致意外行为。

```js theme={null}
import { $ } from "bun";

// 格式化为 Git 命令行标志的恶意输入
const branch = "--upload-pack=echo pwned";

// 不安全：虽然 Bun 安全地将该字符串作为单个参数传递，
// 但 `git` 程序本身会看到并执行恶意标志。
await $`git ls-remote origin ${branch}`;
```

<Note>
  **建议**：在将用户提供的输入作为参数传递给外部命令之前，始终进行清理。验证参数是您应用程序的责任。
</Note>

***

## 致谢

此 API 的大部分部分受到了 [zx](https://github.com/google/zx)、[dax](https://github.com/dsherret/dax) 和 [bnx](https://github.com/wobsoriano/bnx) 的启发。感谢这些项目的作者。
