Bun Shell 讓使用 JavaScript 和 TypeScript 撰寫 shell 腳本變得有趣。它是一個跨平台、類似 bash 的 shell,具有無縫的 JavaScript 互操作性。
快速入門
import { $ } from "bun";
const response = await fetch("https://example.com");
// Use Response as stdin.
await $`cat < ${response} | wc -c`; // 1256
功能:
- 跨平台:適用於 Windows、Linux 和 macOS。您無需安裝額外的依賴項,即可使用 Bun Shell,取代
rimraf
或cross-env
。常見的 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 是用 Zig 編寫的,連同其詞法分析器、剖析器和直譯器。Bun Shell 是一種小型程式語言。
入門
最簡單的 shell 指令是 echo
。要執行它,請使用 $
範本文字標記
import { $ } from "bun";
await $`echo "Hello World!"`; // Hello World!
預設情況下,shell 指令會列印至標準輸出。若要靜音輸出,請呼叫 .quiet()
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // No output
如果你想以文字形式存取指令的輸出呢?請使用 .text()
import { $ } from "bun";
// .text() automatically calls .quiet() for you
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\n
預設情況下,await
會將標準輸出和標準錯誤輸出作為 Buffer
傳回。
import { $ } from "bun";
const { stdout, stderr } = await $`echo "Hello World!"`.quiet();
console.log(stdout); // Buffer(6) [ 72, 101, 108, 108, 111, 32 ]
console.log(stderr); // Buffer(0) []
錯誤處理
預設情況下,非零退出碼會擲出錯誤。此 ShellError
包含有關執行指令的資訊。
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
需要手動檢查。
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)
來設定。
import { $ } from "bun";
// shell promises will not throw, meaning you will have to
// check for `exitCode` manually on every shell command.
$.nothrow(); // equivilent to $.throws(false)
// default behavior, non-zero exit codes will throw an error
$.throws(true);
// alias for $.nothrow()
$.throws(false);
await $`something-that-may-fail`; // No exception thrown
重新導向
指令的輸入或輸出可以使用典型的 Bash 算子重新導向
<
重新導向標準輸入>
或1>
重新導向標準輸出2>
重新導向標準錯誤輸出&>
重新導向標準輸出和標準錯誤輸出>>
或1>>
重新導向標準輸出,附加至目的地,而不是覆寫2>>
重新導向標準錯誤輸出,附加至目的地,而不是覆寫&>>
重新導向標準輸出和標準錯誤輸出,附加至目的地,而不是覆寫1>&2
將標準輸出重新導向至標準錯誤輸出(所有寫入標準輸出的內容將改為寫入標準錯誤輸出)2>&1
將標準錯誤輸出重新導向至標準輸出(所有寫入標準錯誤輸出的內容將改為寫入標準輸出)
Bun Shell 也支援從 JavaScript 物件重新導向和至 JavaScript 物件。
範例:重新導向輸出至 JavaScript 物件 (>
)
若要將 stdout 重新導向至 JavaScript 物件,請使用 >
算子
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,請使用 <
算子
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
(自主體讀取)
範例:重新導向 stdin -> 檔案
import { $ } from "bun";
await $`cat < myfile.txt`;
範例:重新導向 stdout -> 檔案
import { $ } from "bun";
await $`echo bun! > greeting.txt`;
範例:重新導向 stderr -> 檔案
import { $ } from "bun";
await $`bun run index.ts 2> errors.txt`;
範例:重新導向 stderr -> stdout
import { $ } from "bun";
// redirects stderr to stdout, so all output
// will be available on stdout
await $`bun run ./index.ts 2>&1`;
範例:重新導向 stdout -> stderr
import { $ } from "bun";
// redirects stdout to stderr, so all output
// will be available on stderr
await $`bun run ./index.ts 1>&2`;
管道 (|
)
如同在 bash 中,您可以將一個命令的輸出傳遞給另一個命令
import { $ } from "bun";
const result = await $`echo "Hello World!" | wc -w`.text();
console.log(result); // 2\n
您也可以使用 JavaScript 物件進行管道傳遞
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
環境變數
環境變數可以像在 bash 中一樣設定
import { $ } from "bun";
await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\n
你可以使用字串內插法來設定環境變數
import { $ } from "bun";
const foo = "bar123";
await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\n
輸入預設會被跳脫,防止 shell 注入攻擊
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()
來變更單一命令的環境變數。
import { $ } from "bun";
await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // bar
你可以透過呼叫 $.env
來變更所有命令的預設環境變數。
import { $ } from "bun";
$.env({ FOO: "bar" });
// the globally-set $FOO
await $`echo $FOO`; // bar
// the locally-set $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz
你可以透過呼叫沒有參數的 $.env()
來將環境變數重設為預設值。
import { $ } from "bun";
$.env({ FOO: "bar" });
// the globally-set $FOO
await $`echo $FOO`; // bar
// the locally-set $FOO
await $`echo $FOO`.env(undefined); // ""
變更工作目錄
你可以透過將字串傳遞給 .cwd()
來變更命令的工作目錄。
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmp
你可以透過呼叫 $.cwd
來變更所有命令的預設工作目錄。
import { $ } from "bun";
$.cwd("/tmp");
// the globally-set working directory
await $`pwd`; // /tmp
// the locally-set working directory
await $`pwd`.cwd("/"); // /
讀取輸出
若要將命令的輸出讀取為字串,請使用 .text()
import { $ } from "bun";
const result = await $`echo "Hello World!"`.text();
console.log(result); // Hello World!\n
將輸出讀取為 JSON
若要將命令的輸出讀取為 JSON,請使用 .json()
import { $ } from "bun";
const result = await $`echo '{"foo": "bar"}'`.json();
console.log(result); // { foo: "bar" }
逐行讀取輸出
若要逐行讀取命令的輸出,請使用 .lines()
import { $ } from "bun";
for await (let line of $`echo "Hello World!"`.lines()) {
console.log(line); // Hello World!
}
你也可以在已完成的命令上使用 .lines()
import { $ } from "bun";
const search = "bun";
for await (let line of $`cat list.txt | grep ${search}`.lines()) {
console.log(line);
}
將輸出讀取為 Blob
若要將命令的輸出讀取為 Blob,請使用 .blob()
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
:列出目錄中的檔案rm
:移除檔案和目錄echo
:列印文字pwd
:列印工作目錄bun
:在 bun 中執行 buncat
touch
mkdir
which
mv
exit
true
false
yes
seq
dirname
basename
部分已實作
mv
:移動檔案和目錄(缺少跨裝置支援)
尚未實作,但已規劃
- 請參閱 https://github.com/oven-sh/bun/issues/9716 以取得完整清單。
公用程式
Bun Shell 也實作了一組公用程式,用於處理 shell。
$.braces
(大括號展開)
此函式為 shell 指令實作簡單的 大括號展開
import { $ } from "bun";
await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]
$.escape
(轉譯字串)
將 Bun Shell 的轉譯邏輯公開為函式
import { $ } from "bun";
console.log($.escape('$(foo) `bar` "baz"'));
// => \$(foo) \`bar\` \"baz\"
如果您不希望字串被轉譯,請將其包覆在 { raw: 'str' }
物件中
import { $ } from "bun";
await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => baz
.sh 檔案載入器
對於簡單的 shell 指令碼,您可以使用 Bun Shell 來執行 shell 指令碼,而不是 /bin/sh
。
為此,只需使用 bun
執行具有 .sh
副檔名的檔案即可。
echo "Hello World! pwd=$(pwd)"
bun ./script.sh
Hello World! pwd=/home/demo
使用 Bun Shell 的指令碼是跨平台的,這表示它們可以在 Windows 上執行
bun .\script.sh
Hello World! pwd=C:\Users\Demo
實作注意事項
Bun Shell 是使用 Zig 在 Bun 中實作的小型程式語言。它包含手寫的詞法分析器、剖析器和直譯器。與 bash、zsh 和其他 shell 不同,Bun Shell 會並行執行作業。