Skip to content

Bun Shell は、JavaScript と TypeScript を使用したシェルスクリプティングを楽しくします。クロスプラットフォームな bash ライクなシェルで、シームレスな JavaScript 相互運用性を備えています。

クイックスタート:

ts
import { $ } from "bun";

const response = await fetch("https://example.com");

// Response を stdin として使用
await $`cat < ${response} | wc -c`; // 1256

機能

  • クロスプラットフォーム: Windows、Linux、macOS で動作します。rimrafcross-env などの代わりに、追加の依存関係をインストールせずに Bun Shell を使用できます。lscdrm などの一般的なシェルコマンドはネイティブに実装されています。
  • 馴染みやすい: Bun Shell は bash ライクなシェルで、リダイレクト、パイプ、環境変数などをサポートしています。
  • グロブ: ***{expansion} など、グロブパターンをネイティブにサポートしています。
  • テンプレートリテラル: シェルコマンドを実行するためにテンプレートリテラルが使用されます。これにより、変数や式を簡単に挿入できます。
  • 安全性: Bun Shell はデフォルトですべての文字列をエスケープし、シェルインジェクション攻撃を防止します。
  • JavaScript 相互運用: ResponseArrayBufferBlobBun.file(path) および他の JavaScript オブジェクトを stdin、stdout、stderr として使用できます。
  • シェルスクリプティング: Bun Shell はシェルスクリプト(.bun.sh ファイル)を実行するために使用できます。
  • カスタムインタープリター: Bun Shell は Zig で記述されており、レキサー、パーサー、インタープリターと共に実装されています。Bun Shell は小さなプログラミング言語です。

始め方

最もシンプルなシェルコマンドは echo です。実行するには、$ テンプレートリテラルタグを使用します:

js
import { $ } from "bun";

await $`echo "Hello World!"`; // Hello World!

デフォルトでは、シェルコマンドは stdout に出力します。出力を静かにするには、.quiet() を呼び出します:

js
import { $ } from "bun";

await $`echo "Hello World!"`.quiet(); // 出力なし

コマンドの出力にテキストとしてアクセスするにはどうすればよいでしょうか?.text() を使用します:

js
import { $ } from "bun";

// .text() は自動的に .quiet() を呼び出します
const welcome = await $`echo "Hello World!"`.text();

console.log(welcome); // Hello World!\n

デフォルトでは、await は stdout と stderr を Buffer として返します。

js
import { $ } from "bun";

const { stdout, stderr } = await $`echo "Hello!"`.quiet();

console.log(stdout); // Buffer(7) [ 72, 101, 108, 108, 111, 33, 10 ]
console.log(stderr); // Buffer(0) []

エラーハンドリング

デフォルトでは、0 以外の終了コードはエラーをスローします。この ShellError には実行されたコマンドに関する情報が含まれています。

js
import { $ } from "bun";

try {
  const output = await $`something-that-may-fail`.text();
  console.log(output);
} catch (err) {
  console.log(`コード ${err.exitCode} で失敗しました`);
  console.log(err.stdout.toString());
  console.log(err.stderr.toString());
}

スローは .nothrow() で無効にできます。結果の exitCode は手動で確認する必要があります。

js
import { $ } from "bun";

const { stdout, stderr, exitCode } = await $`something-that-may-fail`.nothrow().quiet();

if (exitCode !== 0) {
  console.log(`0 以外の終了コード ${exitCode}`);
}

console.log(stdout);
console.log(stderr);

0 以外の終了コードのデフォルトの処理は、$ 関数自体で .nothrow() または .throws(boolean) を呼び出すことで構成できます。

js
import { $ } from "bun";
// シェルの promise はスローされません。つまり、すべてのシェルコマンドで `exitCode` を手動で確認する必要があります。
$.nothrow(); // $.throws(false) と同等

// デフォルトの動作、0 以外の終了コードはエラーをスローします
$.throws(true);

// $.nothrow() のエイリアス
$.throws(false);

await $`something-that-may-fail`; // 例外はスローされません

リダイレクト

コマンドの 入力 または 出力 は、通常の Bash 演算子を使用して リダイレクト できます:

  • < stdin をリダイレクト
  • > または 1> stdout をリダイレクト
  • 2> stderr をリダイレクト
  • &> stdout と stderr の両方をリダイレクト
  • >> または 1>> stdout をリダイレクト、宛先に 追加(上書きではなく)
  • 2>> stderr をリダイレクト、宛先に 追加(上書きではなく)
  • &>> stdout と stderr の両方をリダイレクト、宛先に 追加(上書きではなく)
  • 1>&2 stdout を stderr にリダイレクト(stdout へのすべての書き込みは stderr になります)
  • 2>&1 stderr を stdout にリダイレクト(stderr へのすべての書き込みは stdout になります)

Bun Shell は JavaScript オブジェクトからのリダイレクトもサポートしています。

例:出力を JavaScript オブジェクトにリダイレクト(>

stdout を JavaScript オブジェクトにリダイレクトするには、> 演算子を使用します:

js
import { $ } from "bun";

const buffer = Buffer.alloc(100);
await $`echo "Hello World!" > ${buffer}`;

console.log(buffer.toString()); // Hello World!\n

以下の JavaScript オブジェクトへのリダイレクトがサポートされています:

  • BufferUint8ArrayUint16ArrayUint32ArrayInt8ArrayInt16ArrayInt32ArrayFloat32ArrayFloat64ArrayArrayBufferSharedArrayBuffer(基盤となるバッファーに書き込み)
  • Bun.file(path)Bun.file(fd)(ファイルに書き込み)

例:JavaScript オブジェクトから入力をリダイレクト(<

JavaScript オブジェクトからの出力を stdin にリダイレクトするには、< 演算子を使用します:

js
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 オブジェクトからのリダイレクトがサポートされています:

  • BufferUint8ArrayUint16ArrayUint32ArrayInt8ArrayInt16ArrayInt32ArrayFloat32ArrayFloat64ArrayArrayBufferSharedArrayBuffer(基盤となるバッファーから読み取り)
  • Bun.file(path)Bun.file(fd)(ファイルから読み取り)
  • Response(本文から読み取り)

例:stdin -> ファイルにリダイレクト

js
import { $ } from "bun";

await $`cat < myfile.txt`;

例:stdout -> ファイルにリダイレクト

js
import { $ } from "bun";

await $`echo bun! > greeting.txt`;

例:stderr -> ファイルにリダイレクト

js
import { $ } from "bun";

await $`bun run index.ts 2> errors.txt`;

例:stderr -> stdout にリダイレクト

js
import { $ } from "bun";

// stderr を stdout にリダイレクトします。すべての出力は stdout で利用可能になります
await $`bun run ./index.ts 2>&1`;

例:stdout -> stderr にリダイレクト

js
import { $ } from "bun";

// stdout を stderr にリダイレクトします。すべての出力は stderr で利用可能になります
await $`bun run ./index.ts 1>&2`;

パイプ(|

bash と同様に、あるコマンドの出力を別のコマンドにパイプできます:

js
import { $ } from "bun";

const result = await $`echo "Hello World!" | wc -w`.text();

console.log(result); // 2\n

JavaScript オブジェクトでもパイプできます:

js
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

コマンド置換($(...)

コマンド置換を使用すると、別のスクリプトの出力を現在のスクリプトに置換できます:

js
import { $ } from "bun";

// 現在のコミットのハッシュを出力
await $`echo Hash of current commit: $(git rev-parse HEAD)`;

これはコマンドの出力のテキスト挿入であり、例えばシェル変数を宣言するために使用できます:

js
import { $ } from "bun";

await $`
  REV=$(git rev-parse HEAD)
  docker built -t myapp:$REV
  echo Done building docker image "myapp:$REV"
`;

NOTE

Bun は内部的に入力テンプレートリテラルで特別な raw プロパティを使用するため、コマンド置換にバッククォート構文を使用すると機能しません:

ts
import { $ } from "bun";

await $`echo \`echo hi\``;

以下を出力する代わりに:

hi

上記は以下を出力します:

echo hi

代わりに $(...) 構文を使用することをお勧めします。


環境変数

環境変数は bash と同様に設定できます:

js
import { $ } from "bun";

await $`FOO=foo bun -e 'console.log(process.env.FOO)'`; // foo\n

文字列挿入を使用して環境変数を設定できます:

js
import { $ } from "bun";

const foo = "bar123";

await $`FOO=${foo + "456"} bun -e 'console.log(process.env.FOO)'`; // bar123456\n

入力はデフォルトでエスケープされ、シェルインジェクション攻撃を防止します:

js
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() を呼び出すことで、単一コマンドの環境変数を変更できます:

js
import { $ } from "bun";

await $`echo $FOO`.env({ ...process.env, FOO: "bar" }); // bar

$.env を呼び出すことで、すべてのコマンドのデフォルトの環境変数を変更できます:

js
import { $ } from "bun";

$.env({ FOO: "bar" });

// グローバルに設定された $FOO
await $`echo $FOO`; // bar

// ローカルに設定された $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz

引数なしで $.env() を呼び出すことで、環境変数をデフォルトにリセットできます:

js
import { $ } from "bun";

$.env({ FOO: "bar" });

// グローバルに設定された $FOO
await $`echo $FOO`; // bar

// ローカルに設定された $FOO
await $`echo $FOO`.env(undefined); // ""

作業ディレクトリの変更

.cwd() に文字列を渡すことで、コマンドの作業ディレクトリを変更できます:

js
import { $ } from "bun";

await $`pwd`.cwd("/tmp"); // /tmp

$.cwd を呼び出すことで、すべてのコマンドのデフォルトの作業ディレクトリを変更できます:

js
import { $ } from "bun";

$.cwd("/tmp");

// グローバルに設定された作業ディレクトリ
await $`pwd`; // /tmp

// ローカルに設定された作業ディレクトリ
await $`pwd`.cwd("/"); // /

出力の読み取り

コマンドの出力を文字列として読み取るには、.text() を使用します:

js
import { $ } from "bun";

const result = await $`echo "Hello World!"`.text();

console.log(result); // Hello World!\n

出力を JSON として読み取り

コマンドの出力を JSON として読み取るには、.json() を使用します:

js
import { $ } from "bun";

const result = await $`echo '{"foo": "bar"}'`.json();

console.log(result); // { foo: "bar" }

出力を行単位で読み取り

コマンドの出力を行単位で読み取るには、.lines() を使用します:

js
import { $ } from "bun";

for await (let line of $`echo "Hello World!"`.lines()) {
  console.log(line); // Hello World!
}

完了したコマンドでも .lines() を使用できます:

js
import { $ } from "bun";

const search = "bun";

for await (let line of $`cat list.txt | grep ${search}`.lines()) {
  console.log(line);
}

出力を Blob として読み取り

コマンドの出力を Blob として読み取るには、.blob() を使用します:

js
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 内で bun を実行
  • cat
  • touch
  • mkdir
  • which
  • mv
  • exit
  • true
  • false
  • yes
  • seq
  • dirname
  • basename

一部 実装:

  • mv: ファイルとディレクトリを移動(クロスデバイスサポートが不足)

まだ 実装されていませんが、計画中:

  • 完全なリストについては Issue #9716 を参照してください。

ユーティリティ

Bun Shell はシェルで作業するための一連のユーティリティも実装しています。

$.braces(ブレース展開)

この関数はシェルコマンドのためのシンプルな ブレース展開 を実装しています:

js
import { $ } from "bun";

await $.braces(`echo {1,2,3}`);
// => ["echo 1", "echo 2", "echo 3"]

$.escape(文字列のエスケープ)

Bun Shell のエスケープロジックを関数として公開します:

js
import { $ } from "bun";

console.log($.escape('$(foo) `bar` "baz"'));
// => \$(foo) \`bar\` \"baz\"

文字列をエスケープしたくない場合は、{ raw: 'str' } オブジェクトでラップします:

js
import { $ } from "bun";

await $`echo ${{ raw: '$(foo) `bar` "baz"' }}`;
// => bun: command not found: foo
// => bun: command not found: bar
// => baz

.sh ファイルローダー

シンプルなシェルスクリプトの場合、/bin/sh の代わりに Bun Shell を使用してシェルスクリプトを実行できます。

そのためには、.sh 拡張子のファイルで bun を使用してスクリプトを実行するだけです。

sh
echo "Hello World! pwd=$(pwd)"
sh
bun ./script.sh
txt
Hello World! pwd=/home/demo

Bun Shell を使用したスクリプトはクロスプラットフォームであり、Windows でも動作します:

powershell
bun .\script.sh
txt
Hello World! pwd=C:\Users\Demo

実装ノート

Bun Shell は Bun 内の小さなプログラミング言語で、Zig で実装されています。手作りのレキサー、パーサー、インタープリターが含まれています。bash、zsh、および他のシェルとは異なり、Bun Shell は操作を並行して実行します。


Bun シェルのセキュリティ

設計により、Bun シェルは システムシェル(/bin/sh など)を呼び出さず、 代わりに同じ Bun プロセス内で実行される bash の再実装であり、 セキュリティを念頭に置いて設計されています。

コマンド引数を解析する際、すべての 挿入された変数 を単一の文字列として扱います。

これは コマンドインジェクション に対して Bun シェルを保護します:

js
import { $ } from "bun";

const userInput = "my-file.txt; rm -rf /";

// 安全:`userInput` は単一の引用符付き文字列として扱われます
await $`ls ${userInput}`;

上記の例では、userInput は単一の文字列として扱われます。これにより、 ls コマンドは「my-file; rm -rf /」という名前の単一のディレクトリのコンテンツを読み取ろうとします。

セキュリティに関する考慮事項

デフォルトではコマンドインジェクションは防止されますが、 開発者は特定のシナリオでもセキュリティに責任があります。

Bun.spawn または node:child_process.exec() API と同様に、意図的に 新しいシェル(例:bash -c)を引数と共に生成するコマンドを実行できます。

これを行うと、制御を委譲し、Bun の組み込みの保護はその新しいシェルによって解釈される文字列には適用されなくなります。

js
import { $ } from "bun";

const userInput = "world; touch /tmp/pwned";

// 危険:`bash -c` で明示的に新しいシェルプロセスを開始しています。
// この新しいシェルは `touch` コマンドを実行します。この方法で渡されたユーザー入力は
// 厳密にサニタイズする必要があります。
await $`bash -c "echo ${userInput}"`;

引数のインジェクション

Bun シェルは、外部コマンドが独自の コマンドライン引数 をどのように解釈するかを知ることはできません。 攻撃者は、ターゲットプログラムが自身のオプションまたはフラグの 1 つとして認識する入力を提供し、 意図しない動作を引き起こす可能性があります。

js
import { $ } from "bun";

// Git コマンドラインフラグとしてフォーマットされた悪意のある入力
const branch = "--upload-pack=echo pwned";

// 危険:Bun は安全に文字列を単一の引数として渡しますが、
// `git` プログラム自体が悪意のあるフラグを見て行動します。
await $`git ls-remote origin ${branch}`;

NOTE

**推奨** — あらゆる言語でのベストプラクティスと同様に、外部コマンドに引数として渡す前に、 常にユーザー提供の入力をサニタイズしてください。引数の検証責任はアプリケーションコードにあります。

クレジット

この API の大部分は zxdax、および bnx からインスピレーションを受けています。これらのプロジェクトの作者に感謝します。

Bun by www.bunjs.com.cn 編集