原文地址(Bun Blog): https://bun.sh/blog/the-bun-shell
作者: 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 中運行
bash
和 sh
等這些 shell
工具已經存在幾十年了。
但是,爲什麼它們在 JavaScript
中不能很好的工作?
macOS (zsh)
、Linux (bash)
和 Windows (cmd)
的 shell 都有所不同,具有不同的語法和不同的命令。每個平臺上可用的命令都不同,甚至相同的命令也可能有不同的可選參數和行爲。
迄今爲止,npm
的解決方案是依靠社區用 JavaScript
實現來填補缺失的命令。
rm -rf 不適用於 Windows
rimraf 是 rm -rf
指令的跨平臺實現,每週下載 6000 萬次:
FOO=bar <script>
設置環境變量在 Windows 上不生效
不同平臺上設置環境變量的方式略有不同。如果不使用 FOO=bar
這種方式,那就是使用 cross-env
which 在 Windows 上是 where
於是另一個周下載量 6000w 的包誕生了:
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 Shell
是 Bun
提供的一種新的實驗性的嵌入式語言和解釋器,支持使用 JavaScript
和 TypeScript
編寫跨平臺運行的 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('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
上運行。我們實現了許多常用命令和功能,如通配符
、環境變量
、重定向(redirection)
、管道(piping)
等。
它被設計爲簡單 shell
腳本的替代品。在 Windows
版 Bun
中,它將爲 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
運行結果 如下