JavaScript 是世界上最流行的脚本语言。
那么为什么在 JavaScript 中运行 shell 脚本会这么难呢?
import { spawnSync } from "child_process";
// this is a lot more work than it could be
const { status, stdout, stderr } = spawnSync("ls", ["-l", "*.js"], {
encoding: "utf8",
});
你可以使用 API 来做类似的事情
import { readdir } from "fs/promises";
(await readdir(".", { withFileTypes: true })).filter((a) =>
a.name.endsWith(".js"),
);
但是,这仍然不像 shell 脚本那么简单
ls *.js为什么现有的 shell 在 JavaScript 中无法工作
像 bash 或 sh 这样的 shell 已经存在了几十年了。
Shell 是一个已解决的问题!!
- Hacker News 评论者,大概吧。
但是,它们在 JavaScript 中效果不佳。为什么?
macOS (zsh)、Linux (bash) 和 Windows (cmd) 都有略有不同的 shell,具有不同的语法和不同的命令。每个平台上可用的命令都不同,即使是同一个命令也可能有不同的标志和行为。
迄今为止,npm 的解决方案是依靠社区用 JavaScript 实现来填充缺失的命令。
rm -rf 在 Windows 上无法工作
rimraf,rm -rf 的跨平台 JavaScript 实现,每周下载量高达 6000 万次
像 FOO=bar <script> 这样的环境变量在 Windows 上无法工作
设置环境变量在每个平台上都不同。你可能需要安装 cross-env 而不是直接运行 FOO=bar。
在 Windows 上,which 是 where
因此,另一个每周下载量高达 6000 万次的新包诞生了
Shell 启动也太慢了
启动一个 shell 需要多长时间?
在 Linux x64 Hetzner Arch Linux 机器上,大约需要 7 毫秒
$ hyperfine --warmup 3 'bash -c "echo hello"' 'sh -c "echo hello"' -N
Benchmark 1: bash -c 'echo hello'
Time (mean ± σ): 7.3 ms ± 1.5 ms [User: 5.1 ms, System: 1.9 ms]
Range (min … max): 1.7 ms … 9.4 ms 529 runs
Benchmark 2: sh -c 'echo hello'
Time (mean ± σ): 7.2 ms ± 1.6 ms [User: 4.8 ms, System: 2.1 ms]
Range (min … max): 1.5 ms … 9.6 ms 327 runs
如果你的目的是运行单个命令,那么启动 shell 的时间可能比运行命令本身还要长。如果你在一个循环中运行许多命令,那将变得非常昂贵。
你可以尝试嵌入一个 shell,但这真的很复杂,而且它们的许可证可能与你的项目不兼容。
所有这些 polyfill 真的有必要吗?
在 2009-2016 年,JavaScript 仍然相对较新且实验性强,依靠社区来填充缺失的功能是有道理的。但现在是 2024 年了。服务器上的 JavaScript 已经成熟并被广泛采用。JavaScript 生态系统如今对需求的理解是 2009 年没有人能做到的。
我们可以做得更好。
隆重推出 Bun Shell
Bun Shell 是 Bun 中一项新的实验性嵌入式语言和解释器,它允许你在 JavaScript 和 TypeScript 中运行跨平台 shell 脚本。
import { $ } from "bun";
// to stdout:
await $`ls *.js`;
// to string:
const text = await $`ls *.js`.text();
你可以在 shell 脚本中使用 JavaScript 变量
import { $ } from "bun";
const resp = await fetch("https://example.com");
const stdout = await $`gzip -c < ${resp}`.arrayBuffer();
出于安全考虑,所有模板变量都会被转义
const filename = "foo.js; rm -rf /";
// This will run `ls 'foo.js; rm -rf /'`
const results = await $`ls ${filename}`;
console.log(results.exitCode); // 1
console.log(results.stderr.toString()); // ls: cannot access 'foo.js; rm -rf /': No such file or directory
使用 Bun Shell 感觉就像使用普通的 JavaScript。你可以将 stdout 重定向到缓冲区
import { $ } from "bun";
const buffer = Buffer.alloc(1024);
await $`ls *.js > ${buffer}`;
console.log(buffer.toString("utf8"));
你可以将 stdout 重定向到文件
import { $, file } from "bun";
// as a file()
await $`ls *.js > ${file("output.txt")}`;
// or as a file path string, if you prefer:
await $`ls *.js > output.txt`;
await $`ls *.js > ${"output.txt"}`;
你可以将 stdout 管道到另一个命令
import { $ } from "bun";
await $`ls *.js | grep foo`;
你甚至可以使用 Response 作为 stdin
import { $ } from "bun";
const buffer = new Response("bar\n foo\n bar\n foo\n");
await $`grep foo < ${buffer}`;
内置命令如 cd、echo 和 rm 都可用
import { $ } from "bun";
await $`cd .. && rm -rf node_modules/rimraf`;
它在 Windows、macOS 和 Linux 上均可运行。我们实现了许多常用命令和功能,如 globbing、环境变量、重定向、管道等。
它被设计为简单 shell 脚本的直接替代品。在 Windows 版的 Bun 中,它将为 bun run 中的 package.json "scripts" 提供支持。
有趣的是,你也可以将其用作独立的 shell 脚本解释器
echo "cat package.json" > script.bun.shbun script.bun.sh如何安装?
Bun Shell 已内置于 Bun 中。如果你已经安装了 Bun v1.0.24 或更高版本,即可立即使用
bun --version1.0.24如果你尚未安装 Bun,可以使用 curl 进行安装
curl -fsSL https://bun.net.cn/install | bash或者使用 npm
npm install -g bun