Bun Shell 은 JavaScript & TypeScript 로 셸 스크립팅을 재미있게 만듭니다. 크로스 플랫폼 bash 유사 셸이며 원활한 JavaScript 상호 운용성을 제공합니다.
퀵스타트:
import { $ } from "bun";
const response = await fetch("https://example.com");
// Response 를 stdin 으로 사용.
await $`cat < ${response} | wc -c`; // 1256기능
- 크로스 플랫폼: Windows, Linux & macOS 에서 작동합니다.
rimraf나cross-env대신 추가 의존성 없이 Bun Shell 을 사용할 수 있습니다.ls,cd,rm과 같은 일반적인 셸 명령은 네이티브로 구현됩니다. - 익숙함: Bun Shell 은 bash 유사 셸로, 리디렉션, 파이프, 환경 변수 등을 지원합니다.
- 글롭:
**,*,{expansion}등을 포함한 글롭 패턴을 네이티브로 지원합니다. - 템플릿 리터럴: 템플릿 리터럴을 사용하여 셸 명령을 실행합니다. 이를 통해 변수와 표현식을 쉽게 인터폴레이션할 수 있습니다.
- 안전성: Bun Shell 은 기본적으로 모든 문자열을 이스케이프하여 셸 인젝션 공격을 방지합니다.
- JavaScript 상호 운용성:
Response,ArrayBuffer,Blob,Bun.file(path)및 기타 JavaScript 객체를 stdin, stdout, stderr 로 사용할 수 있습니다. - 셸 스크립팅: Bun Shell 은 셸 스크립트 (
.bun.sh파일) 를 실행하는 데 사용할 수 있습니다. - 커스텀 인터프리터: Bun Shell 은 Zig 로 작성되었으며 렉서, 파서, 인터프리터를 포함합니다. Bun Shell 은 작은 프로그래밍 언어입니다.
시작하기
가장 간단한 셸 명령은 echo 입니다. 실행하려면 $ 템플릿 리터럴 태그를 사용하세요:
import { $ } from "bun";
await $`echo "Hello World!"`; // Hello World!기본적으로 셸 명령은 stdout 에 출력합니다. 출력을 조용히 하려면 .quiet() 를 호출하세요:
import { $ } from "bun";
await $`echo "Hello World!"`.quiet(); // 출력 없음명령의 출력에 텍스트로 접근하려면 어떻게 할까요? .text() 를 사용하세요:
import { $ } from "bun";
// .text() 는 자동으로 .quiet() 를 호출합니다
const welcome = await $`echo "Hello World!"`.text();
console.log(welcome); // Hello World!\n기본적으로 await 은 stdout 과 stderr 를 Buffer 로 반환합니다.
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 이 아닌 종료 코드는 오류를 throw 합니다. 이 ShellError 는 실행된 명령에 대한 정보를 포함합니다.
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() 를 사용하여 throw 를 비활성화할 수 있습니다. 결과의 exitCode 를 수동으로 확인해야 합니다.
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) 를 호출하여 구성할 수 있습니다.
import { $ } from "bun";
// 셸 promise 는 throw 하지 않으므로 모든 셸 명령에서 `exitCode` 를 수동으로 확인해야 합니다.
$.nothrow(); // $.throws(false) 와 동일
// 기본 동작, 0 이 아닌 종료 코드는 오류를 throw 합니다
$.throws(true);
// $.nothrow() 의 별칭
$.throws(false);
await $`something-that-may-fail`; // 예외 throw 되지 않음리디렉션
명령의 입력 또는 출력 은 일반적인 Bash 연산자를 사용하여 리디렉션 될 수 있습니다:
<stdin 리디렉션>또는1>stdout 리디렉션2>stderr 리디렉션&>stdout 과 stderr 모두 리디렉션>>또는1>>stdout 리디렉션, 대상에 추가 (덮어쓰기 대신)2>>stderr 리디렉션, 대상에 추가 (덮어쓰기 대신)&>>stdout 과 stderr 모두 리디렉션, 대상에 추가 (덮어쓰기 대신)1>&2stdout 을 stderr 로 리디렉션 (stdout 에 대한 모든 쓰기는 stderr 로 이동)2>&1stderr 을 stdout 으로 리디렉션 (stderr 에 대한 모든 쓰기는 stdout 으로 이동)
Bun Shell 은 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";
// stderr 을 stdout 으로 리디렉션하여 모든 출력을
// stdout 에서 사용할 수 있습니다
await $`bun run ./index.ts 2>&1`;예제: stdout -> stderr 리디렉션
import { $ } from "bun";
// stdout 을 stderr 으로 리디렉션하여 모든 출력을
// 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\nJavaScript 객체와도 파이프할 수 있습니다:
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명령 치환 ($(...))
명령 치환을 사용하면 다른 스크립트의 출력을 현재 스크립트에 치환할 수 있습니다:
import { $ } from "bun";
// 현재 커밋의 해시 출력
await $`echo Hash of current commit: $(git rev-parse HEAD)`;이는 명령 출력의 텍스트 삽입이며 예를 들어 셸 변수를 선언하는 데 사용할 수 있습니다:
import { $ } from "bun";
await $`
REV=$(git rev-parse HEAD)
docker built -t myapp:$REV
echo Done building docker image "myapp:$REV"
`;NOTE
Bun 은 입력 템플릿 리터럴에서 특수한 raw 속성을 internally 사용하므로 명령 치환에 백틱 구문을 사용하면 작동하지 않습니다:
import { $ } from "bun";
await $`echo \`echo hi\``;다음을 출력하는 대신:
hi위 코드는 다음을 출력합니다:
echo hi대신 $(...) 구문을 사용하는 것을 권장합니다.
환경 변수
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입력은 기본적으로 이스케이프되어 셸 인젝션 공격을 방지합니다:
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" });
// 전역으로 설정된 $FOO
await $`echo $FOO`; // bar
// 로컬로 설정된 $FOO
await $`echo $FOO`.env({ FOO: "baz" }); // baz$.env() 를 인수 없이 호출하여 환경 변수를 기본값으로 재설정할 수 있습니다:
import { $ } from "bun";
$.env({ FOO: "bar" });
// 전역으로 설정된 $FOO
await $`echo $FOO`; // bar
// 로컬로 설정된 $FOO
await $`echo $FOO`.env(undefined); // ""작업 디렉터리 변경
.cwd() 에 문자열을 전달하여 명령의 작업 디렉터리를 변경할 수 있습니다:
import { $ } from "bun";
await $`pwd`.cwd("/tmp"); // /tmp$.cwd 를 호출하여 모든 명령의 기본 작업 디렉터리를 변경할 수 있습니다:
import { $ } from "bun";
$.cwd("/tmp");
// 전역으로 설정된 작업 디렉터리
await $`pwd`; // /tmp
// 로컬로 설정된 작업 디렉터리
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 에서 bun 실행cattouchmkdirwhichmvexittruefalseyesseqdirnamebasename
부분적으로 구현됨:
mv: 파일 및 디렉터리 이동 (크로스 디바이스 지원 누락)
아직 구현되지 않았지만 계획됨:
- 전체 목록은 이슈 #9716 을 참조하세요.
유틸리티
Bun Shell 은 셸 작업을 위한 일련의 유틸리티도 구현합니다.
$.braces (중괄호 확장)
이 함수는 셸 명령에 대한 간단한 중괄호 확장 을 구현합니다:
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 파일 로더
간단한 셸 스크립트의 경우 /bin/sh 대신 Bun Shell 을 사용하여 셸 스크립트를 실행할 수 있습니다.
그러려면 .sh 확장자가 있는 파일에서 bun 으로 스크립트를 실행하면 됩니다.
echo "Hello World! pwd=$(pwd)"bun ./script.shHello World! pwd=/home/demoBun Shell 을 사용한 스크립트는 크로스 플랫폼이므로 Windows 에서도 작동합니다:
bun .\script.shHello World! pwd=C:\Users\Demo구현 노트
Bun Shell 은 Bun 에서 Zig 로 구현된 작은 프로그래밍 언어입니다. 수작업으로 작성한 렉서, 파서, 인터프리터를 포함합니다. bash, zsh 및 기타 셸과 달리 Bun Shell 은 작업을 동시에 실행합니다.
Bun 셸의 보안
설계상 Bun 셸은 시스템 셸 (예: /bin/sh) 을 호출하지 않으며 대신 보안을 염두에 두고 설계된 동일한 Bun 프로세스에서 실행되는 bash 의 재구현입니다.
명령 인수를 파싱할 때 모든 인터폴레이션된 변수 를 단일 리터럴 문자열로 처리합니다.
이는 Bun 셸을 명령 인젝션으로부터 보호합니다:
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 의 내장 보호 기능이 새 셸에서 해석된 문자열에는 더 이상 적용되지 않습니다.
import { $ } from "bun";
const userInput = "world; touch /tmp/pwned";
// 안전하지 않음: 명시적으로 `bash -c` 로 새 셸 프로세스를 시작했습니다.
// 이 새 셸은 `touch` 명령을 실행합니다. 이 방식으로 전달된 모든 사용자 입력은 철저히 검증되어야 합니다.
await $`bash -c "echo ${userInput}"`;인젝션 인젝션
Bun 셸은 외부 명령이 자체 명령줄 인수를 어떻게 해석하는지 알 수 없습니다. 공격자는 대상 프로그램이 자체 옵션 또는 플래그로 인식하는 입력을 제공하여 의도하지 않은 동작을 유발할 수 있습니다.
import { $ } from "bun";
// Git 명령줄 플래그로 포맷된 악성 입력
const branch = "--upload-pack=echo pwned";
// 안전하지 않음: Bun 은 문자열을 단일 인수로 안전하게 전달하지만
// `git` 프로그램 자체는 악성 플래그를 보고 작동합니다.
await $`git ls-remote origin ${branch}`;NOTE
**권장 사항** — 모든 언어에서 모범 사례이듯이 외부 명령의 인수로 전달하기 전에 항상 사용자 제공 입력을 검증하세요. 인수를 검증하는 책임은 애플리케이션 코드에 있습니다.크레딧
이 API 의 많은 부분은 zx, dax, bnx 에서 영감을 받았습니다. 이 프로젝트의 저자들에게 감사드립니다.