原文地址(Bun Blog): bun.sh/blog/the-bu…
作者: jarredsumner
发布时刻:2024-01-20

前语

JavaScript 是世界上最流行的脚本言语。

那么为什么在 JavaScript 中履行 Shell 脚本很困难呢?

import { spawnSync } from 'child_process'
// 代码比想象中要略微复杂一些
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 中运转

bashsh 等这些 shell 东西现已存在几十年了。

可是,为什么它们在 JavaScript 中不能很好的作业?

macOS (zsh)Linux (bash)Windows (cmd) 的 shell 都有所不同,具有不同的语法和不同的指令。每个渠道上可用的指令都不同,甚至相同的指令也或许有不同的可选参数和行为。

迄今为止,npm 的解决方案是依托社区用 JavaScript 完成来添补缺失的指令。

rm -rf 不适用于 Windows

rimrafrm -rf 指令的跨渠道完成,每周下载 6000 万次:

译:运用 Bun 履行 Shell 脚本

FOO=bar <script> 设置环境变量在 Windows 上不生效

不同渠道上设置环境变量的方法略有不同。假如不运用 FOO=bar 这种方法,那就是运用 cross-env

译:运用 Bun 履行 Shell 脚本

which 在 Windows 上是 where

于是另一个周下载量 6000w 的包诞生了:

译:运用 Bun 履行 Shell 脚本

shell 发动时刻也有一点长

创建一个 shell 履行需要多久?

在 Linux x64 Hetzner Arch Linux 机器上,大约需要 7ms:

$ 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 ShellBun 供给的一种新的试验性的嵌入式言语和解说器,支持运用 JavaScriptTypeScript 编写跨渠道运转的 shell 脚本。

import { $ } from 'bun'
// 直接在终端里输出
await $`ls *.js`
// 转为字符串变量
const text = await $`ls *.js`.text()

一起答应你运用 JavaScript 变量:

import { $ } from 'bun'
const resp = await fetch('https://example.com')
const stdout = await $`gzip -c < ${resp}`.arrayBuffer()

出于安全问题考虑,所有模板变量都将被转义:

const filename = 'foo.js; rm -rf /'
// 将会履行指令 `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。答应你将规范输出放入 buffers 中:

import { $ } from 'bun'
const buffer = Buffer.alloc(1024)
await $`ls *.js > ${buffer}`
console.log(buffer.toString('utf8'))

你也能够将输出成果直接写入文件:

import { $, file } from 'bun'
// 作为文件输出
await $`ls *.js > ${file('output.txt')}`
// 或许文件路径字符串
await $`ls *.js > output.txt`
await $`ls *.js > ${'output.txt'}`

你还能够将输出成果经过管道运算符传递给其它指令:

import { $ } from 'bun'
await $`ls *.js | grep foo`

你甚至能够运用 Response 作为规范输入:

import { $ } from 'bun'
const buffer = new Response('barn foon barn foon')
await $`grep foo < ${buffer}`

可运用 cdechorm 等内置指令:

import { $ } from 'bun'
await $`cd .. && rm -rf node_modules/rimraf`

它可在 WindowsmacOSLinux 上运转。咱们完成了许多常用指令和功能,如通配符环境变量重定向(redirection)管道(piping)等。

它被设计为简略 shell 脚本的替代品。在 WindowsBun 中,它将为 bun run 中的 package.json “脚本”供给支持。

为了更风趣一点,您还能够将它用作独立的 shell 脚本解说器:

echo "cat package.json" > script.bun.sh
bun script.bun.sh

怎么装置?

Bun Shell 内置于 Bun 中。假如现已装置了 Bun v1.0.24 或更高版别,那么你就能够运用它:

bun --version
1.0.24

假如你没有装置Bun,能够运用curl装置:

curl -fsSL https://bun.sh/install | bash

或许运用 npm :

npm install -g bun

运用实践

创建 test.ts 文件,写入如下代码

import { $ } from 'bun'
await $`echo hello world`
const files = await $`ls *.js *.mjs`.text()
console.log(files.split('n'))

运转脚本

bun test.ts

运转成果 如下

译:运用 Bun 履行 Shell 脚本