譯:使用 Bun 執行 Shell 腳本

原文地址(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 中運行

bashsh 等這些 shell 工具已經存在幾十年了。

但是,爲什麼它們在 JavaScript 中不能很好的工作?

macOS (zsh)Linux (bash)Windows (cmd) 的 shell 都有所不同,具有不同的語法和不同的命令。每個平臺上可用的命令都不同,甚至相同的命令也可能有不同的可選參數和行爲。

迄今爲止,npm 的解決方案是依靠社區用 JavaScript 實現來填補缺失的命令。

rm -rf 不適用於 Windows

rimrafrm -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 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('bar\n foo\n bar\n foo\n')

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

運行結果 如下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章