Bun Shell: 내 스튜디오의 모든 Bash 스크립트를 대체했습니다
요약
작성자는 수년간의 개발 과정에서 Bash 스크립트가 가진 인용구 버그, 환경 의존성 문제(jq, awk 등), 플랫폼별 비호환성 등의 근본적인 문제점을 겪어왔습니다. 이에 대한 해결책으로 Bun Shell을 도입하여 기존의 방대한 Bash 코드를 TypeScript 기반의 타입 안전하고 이식성이 뛰어난 스크립트로 마이그레이션했습니다. Bun Shell은 자동 이스케이프, 체인 가능한 Promise 반환 값 처리(.text(), .json() 등), 그리고 최소한의 의존성으로 빠르고 안정적인 개발 환경을 제공하며, 기존의 복잡한 쉘 유틸리티(jq, awk)를 대체합니다.
핵심 포인트
- Bash 스크립트는 인용구 버그, `set -e` 누락 등으로 인해 예측 불가능하고 디버깅이 어렵다.
- 플랫폼 간 호환성 문제(GNU coreutils vs BSD coreutils, jq 버전 차이 등)가 크로스 플랫폼 개발의 큰 장애물이다.
- Bun Shell은 타입화된 인수 처리와 자동 이스케이프 기능을 제공하여 쉘 스크립트의 안정성을 극대화한다.
- 반환 값 처리가 체인 가능한 Promise 형태로 제공되어 `.text()`, `.json()` 등의 메서드를 통해 데이터 파싱 및 후속 처리가 용이하다.
- Bun은 자체 최소 쉘을 제공하며, 파일 처리나 JSON 파싱 등에서 기존에 필요했던 `jq`, `awk` 같은 외부 의존성을 상당 부분 대체한다.
1,400 줄의 bash 와 23 개의 스크립트를 삭제하고, Bun Shell TypeScript 로 교체했습니다. Bun Shell ($ ... ) 은 타입화된 인수, 자동 이스케이프, .text(), .json(), .lines() 를 기본 제공하며 jq, awk, 그리고 대부분의 GNU coreutils 의존성을 제거합니다. 스크립트는 이제 약 30ms 만에 부팅됩니다. Bash 는 여전히 인터랙티브 REPL 과 매우 긴 파이프라인에서 우위를 점하지만, 나머지는 모두 Bun 으로 이동했습니다.
저는 과거에 모든 프로젝트에 scripts/ 폴더를 유지했습니다. 그것은 항상 썩었습니다. 인용구 버그, set -e 누락, 그리고 기계 간에 일치하지 않는 jq 버전들입니다. 지난 분기에는 23 개를 Bun Shell 로 마이그레이션하고 오후 한 번에 1,400 줄의 bash 를 삭제했습니다.
왜 Bash 스크립트는 항상 혼자 스튜디오에서 썩는가?
저는 많은 글루 코드를 작성합니다. 빌드 푸시, 변경 로그 게시, CDN 에 이미지 미러링, 두 개의 env 파일 차이 비교, 로그 꼬임 및 정규식 검색을 수행합니다. 오랫동안 그것은 .sh 파일로 살았습니다. 작성한 날에는 작동했지만, 6 주 후에 재현할 수 없는 이유로 깨졌습니다.
Bash 는 4 가지 문제를 계속 맞았습니다. 인용구가 그 중 하나입니다. "$file" versus $file versus '$file' 이 중요하며, 차이가 공간이 있는 경로가 나타날 때까지 보이지 않습니다. set -euo pipefail 은 필수적이지만 스크립트 번호 12 에는 아무도 기억하지 못합니다. 오류는 조용히 삼키며, 3 주 후에 밤새 작업이 빈 파일을 작성하고 있음을 발견합니다. 그리고 타입은 없으므로 --dry-run versus -n versus --dry 는 항상 동전 던지기입니다.
포터블리티 스토리는 더 나쁩니다. 내 노트북에는 Home brew 를 통해 GNU coreutils 가 있습니다. CI 런너는 BSD coreutils 입니다. sed -i 는 다른 인수를 사용합니다. 날짜 형식은 다릅니다. jq 는 한 박스는 1.6, 다른 박스는 1.7 이었고, 하나의 스크립트가 1.7 전용 플래그에 의존하고 있었지만 제가 알아채지 못했습니다. 모든 크로스 플랫폼 수정은 또 다른 if [[ "$OSTYPE" == "darwin"* ]] 분지를 추가합니다.
혼자 스튜디오의 살인자는 23:00 에 60 줄의 shell 스크립트를 디버깅할 수 없다는 것입니다. 저는 스크립트가 실행되거나 정확히 지정한 줄을 가리키는 스택 트레이스로 쾅쾅하게 실패해야 합니다. Bash 는 두 가지도 주지 않습니다.
저는 Python 을 오랫동안 시도했습니다. venv plus boto3 plus requests 의 시작 시간은 차가운 800ms 로, 이는 좋지만 hot loop 안에 호출할 때까지입니다. 저는 zx (Node + JS 템플릿 문자열) 를 시도했습니다. 작동했지만 모든 스크립트 폴더에 Node, npm, 그리고 node_modules 디렉토리를 끌었습니다. 그리고 Bun 1.2 가 모든 새로운 RAXXO 프로젝트에서 Node 를 대체하고, 저는 거의 우연히 Bun Shell 을 시도했습니다.
Bun Shell 정신 모델
Bun 은 태그된 템플릿으로 내장 shell 을 제공합니다. import $ from bun 과 명령어를 인라인으로 작성합니다:
import { $ } from "bun";
const branch = await $ git rev-parse --abbrev-ref HEAD.text();
console.log(Deploying ${branch.trim()});
세 가지가 bash 를 시작하는 것에서 다릅니다. 인수는 인터폴레이션 시 자동으로 이스케이프되므로, $ mv ${userInput} /tmp` 는 ; rm -rf / 로 속임수를 당할 수 없습니다. 반환 값은 .text(), .json(), .lines(), .blob(), .arrayBuffer() 와 같은 체인 가능한 프롬리스이며, .quiet(), .nothrow(), .cwd(path), .env({...}) 와 같은 수정자도 있습니다. 그리고 macOS, Linux, Windows 에서 동일하게 작동하며 Bun 은 /bin/sh 를 감싸는 래퍼가 아니라 자체 최소 shell 을 제공합니다.
저가 가장 많이 사용하는 패턴 몇 가지:
javascript // 명령어에서 JSON 직접 읽기
const tags = await $ git tag --list .lines();
// 출력을 억제하고, 0 이 아닌 경우에도 던지지 않음
const result = await $ grep ERROR app.log .quiet().nothrow();
if (result.exitCode === 0) await alert(result.stdout.toString());
// 명령어 간 파이프링, 여전히
inside template await $ cat data.csv | sort -u > clean.csv ; Cold startup on my M2 is around 30ms for a Bun script that does one shell call, versus around 250ms for the equivalent zx script and around 800ms for Python with boto3 . For a script that runs inside a watch loop or a git hook, that gap matters. The thing I did not expect: I no longer need jq , awk , sed , cut , or tr for most jobs. Bun ships Bun.file() , JSON.parse , regex, and .replaceAll() . A bash one-liner like cat events.json | jq '.[].user' | sort -u | wc -l becomes: javascript const events = await Bun.file(
macOS 에서 느린 프로세스를 프로파일링할 때 dtrace | grep | awk | sort | uniq -c | sort -rn | head 와 같은 파이프라인을 사용한다. 이를 TypeScript 로 재작성하는 것은 아무런 도움이 되지 않는다. 파이프 6 개로 연결된 바이너니는 bash 의 영역이다. 나는 이 것을 .sh 파일로 유지하고, 필요할 때 Bun 에서 호출한다. POSIX 만 지원되는 환경들. 몇몇 CI 러너와 가끔 Docker 기본 이미지는 Bun 을 설치하지 않았고, 내가 추가할 수 없다. 그들을 위해 Bash 를 사용하여 Bun 을 설치한 후 전달하는 작은 부트스트랩 스크립트를 유지한다. 부트스트랩은 12 줄이며 6 개월 동안 변경되지 않았다. 긴 파이프라인에 대한 우회책은 간단하다: Bun Shell 은 bash 자체를 포함한 임의의 바이너니를 호출할 수 있다. 내가 정말로 200 자의 파이프라인을 원한다면, await $ bash -c "${pipeline}"`` 을 작성하고 Bun 이 스폰, 출력 포착, 에러 코드를 처리한다. 나는 프로젝트당 하나 또는 두 개의 것을 유지하며 잘 주석 처리하고 예외로 취급한다. 또한 나의 pre-commit hook 은 Bash 로 유지한다. 2 년 동안 6 줄이며 재작성하는 것은 허영심이다. 마이그레이션의 좋은 부수 효과: my scripts/ 폴더는 이제 프로젝트의 나머지 TypeScript 와 함께 배송된다. 동일한 tsconfig.json 이 이를 린트한다. 동일한 Biome 규칙이 이를 포맷팅한다. 스크립트와 테스트 파일에서 사용되는 함수를 이름 변경할 때, LSP 가 둘 다 감지한다. Bash 는 나에게 그걸 주지 않았다. Bottom Line 나는 다시 돌아가지 않을 것이다. Bun Shell 은 Python 과 zx 가 결코 제대로 하지 못했던 곳에 맞았다: 타입화된 인수, 자동 이스케이프, node_modules 없음, ~30ms cold start, 그리고 macOS, Linux, CI 러너 모두에서 조건부 브랜치 없이 동일한 스크립트 작동한다. 마이그레이션은 영웅적인 재작성이 아니었다. 나는 커피 한 잔당 하나의 스크립트를 작성하고, Bash 버전을 git history 에 유지하며, 나머지는 단순한 pre-commit hook 이거나 파이프라인으로 더 나은 경우를 멈췄다. 만약 당신이 솔로 스튜디오를 운영한다면, 당신의 deploy 또는 notifier 스크립트부터 시작할 것이다. 그 것들은 금요일 밤 11 시에 깨지고 가장 많은 비용이 든 디버깅을 한다. Bun 이 나의 스택에 어떻게 들어가는지에 대한 나머지는 Bun 의 테스트 러너가 새로운 프로젝트에서 Vitest 을 대체하며, 테스트 측면을 커버하고 Studio 는 이 스택이 실제로 배송하는 프로젝트를 보여준다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기