Enhanced shell scripting with template literals, cross-platform compatibility, streaming I/O, and comprehensive process control for modern JavaScript shell operations.
import { $ } from "bun";Execute shell commands using template literal syntax with automatic escaping, expression interpolation, and promise-based execution.
/**
* Execute shell commands with template literal syntax
* @param strings - Template strings array
* @param expressions - Interpolated expressions
* @returns ShellPromise for command execution
*/
function $(strings: TemplateStringsArray, ...expressions: ShellExpression[]): $.ShellPromise;
type ShellExpression =
| { toString(): string }
| Array<ShellExpression>
| string
| { raw: string }
| Subprocess
| ReadableStream
| WritableStream;Usage Examples:
import { $ } from "bun";
// Basic command execution
const result = await $`echo "Hello World"`;
console.log(result.stdout.toString()); // "Hello World\n"
// Variable interpolation with automatic escaping
const filename = "my file.txt";
await $`touch ${filename}`; // Automatically escapes spaces
// Raw string interpolation (no escaping)
const command = "ls -la";
await $`${$.raw(command)}`; // Executes as literal command
// Expression interpolation
const files = ["file1.txt", "file2.txt"];
await $`echo ${files.join(" ")}`; // "file1.txt file2.txt"
// Piping between commands
const output = await $`ls -la | grep ".txt" | wc -l`;
console.log(`Found ${output.text().trim()} .txt files`);Configure shell behavior including environment variables, working directory, error handling, and output control.
/**
* Shell configuration methods for customizing execution environment
*/
namespace $ {
/**
* Perform bash-like brace expansion on patterns
* @param pattern - Brace pattern to expand
* @returns Array of expanded strings
*/
function braces(pattern: string): string[];
/**
* Escape strings for safe input into shell commands
* @param input - String to escape
* @returns Escaped string safe for shell execution
*/
function escape(input: string): string;
/**
* Configure default environment variables for shells
* @param newEnv - Default environment variables
* @returns Configured $ instance
*/
function env(newEnv?: Record<string, string | undefined>): $;
/**
* Configure default working directory for shells
* @param newCwd - Default working directory
* @returns Configured $ instance
*/
function cwd(newCwd?: string): $;
/**
* Configure shell to not throw exceptions on non-zero exit codes
* @returns Configured $ instance
*/
function nothrow(): $;
/**
* Configure whether shell should throw exceptions on non-zero exit codes
* @param shouldThrow - Whether to throw on non-zero exit codes
* @returns Configured $ instance
*/
function throws(shouldThrow: boolean): $;
}Usage Examples:
// Environment variable configuration
const customShell = $.env({
PATH: "/usr/local/bin:/usr/bin:/bin",
NODE_ENV: "production"
});
await customShell`echo $NODE_ENV`; // "production"
// Working directory configuration
const projectShell = $.cwd("/path/to/project");
await projectShell`pwd`; // "/path/to/project"
// Error handling configuration
const safeShell = $.nothrow();
const result = await safeShell`exit 1`; // Won't throw, returns result with exitCode: 1
// Brace expansion
const patterns = $.braces("file.{js,ts,jsx,tsx}");
console.log(patterns); // ["file.js", "file.ts", "file.jsx", "file.tsx"]
// String escaping
const userInput = "'; rm -rf /; echo '";
const safe = $.escape(userInput);
await $`echo ${$.raw(safe)}`; // Safely escapedAdvanced shell promise interface with streaming I/O, output formatting, and execution control for complex shell operations.
/**
* Shell promise class for executing and controlling shell commands
*/
class ShellPromise extends Promise<ShellOutput> {
/** Writable stream for stdin input */
get stdin(): WritableStream;
/**
* Change working directory for this command
* @param newCwd - New working directory
* @returns ShellPromise instance for chaining
*/
cwd(newCwd: string): this;
/**
* Set environment variables for this command
* @param newEnv - Environment variables
* @returns ShellPromise instance for chaining
*/
env(newEnv: Record<string, string> | undefined): this;
/**
* Configure command to only buffer output (no echoing to console)
* @returns ShellPromise instance for chaining
*/
quiet(): this;
/**
* Read stdout line by line as an async iterable
* @returns AsyncIterable of stdout lines
*/
lines(): AsyncIterable<string>;
/**
* Read stdout as a string with optional encoding
* @param encoding - Text encoding for output
* @returns Promise resolving to stdout string
*/
text(encoding?: BufferEncoding): Promise<string>;
/**
* Read stdout as parsed JSON object
* @returns Promise resolving to parsed JSON
*/
json(): Promise<any>;
/**
* Read stdout as ArrayBuffer
* @returns Promise resolving to ArrayBuffer
*/
arrayBuffer(): Promise<ArrayBuffer>;
/**
* Read stdout as Blob
* @returns Promise resolving to Blob
*/
blob(): Promise<Blob>;
/**
* Configure this command to not throw on non-zero exit codes
* @returns ShellPromise instance for chaining
*/
nothrow(): this;
/**
* Configure whether this command should throw on non-zero exit codes
* @param shouldThrow - Whether to throw on non-zero exit codes
* @returns ShellPromise instance for chaining
*/
throws(shouldThrow: boolean): this;
}
interface ShellOutput {
readonly stdout: Buffer;
readonly stderr: Buffer;
readonly exitCode: number;
text(encoding?: BufferEncoding): string;
json(): any;
arrayBuffer(): ArrayBuffer;
blob(): Blob;
}Usage Examples:
// Streaming output processing
const command = $`find . -name "*.js" -type f`;
// Process lines as they come
for await (const line of command.quiet().lines()) {
console.log(`Found: ${line}`);
}
// Different output formats
const textOutput = await $`cat package.json`.text();
const jsonOutput = await $`cat package.json`.json();
const binaryOutput = await $`cat image.png`.arrayBuffer();
// Environment and directory configuration
const result = await $`pwd`
.cwd("/tmp")
.env({ CUSTOM_VAR: "value" })
.quiet();
// Error handling with nothrow
const safeResult = await $`ls /nonexistent`.nothrow();
if (safeResult.exitCode !== 0) {
console.log("Directory not found:", safeResult.stderr.toString());
}
// Stdin piping
const proc = $`grep "error"`.quiet();
const writer = proc.stdin.getWriter();
await writer.write(new TextEncoder().encode("info: success\nerror: failed\n"));
await writer.close();
const output = await proc.text();
console.log(output); // "error: failed\n"Comprehensive error handling with detailed error information, output capture, and customizable exception behavior.
/**
* Shell error class for command execution failures
*/
class ShellError extends Error implements ShellOutput {
/** Standard output buffer */
readonly stdout: Buffer;
/** Standard error buffer */
readonly stderr: Buffer;
/** Process exit code */
readonly exitCode: number;
/**
* Read stdout as string
* @param encoding - Text encoding
* @returns Stdout as string
*/
text(encoding?: BufferEncoding): string;
/**
* Read stdout as parsed JSON
* @returns Parsed JSON object
*/
json(): any;
/**
* Read stdout as ArrayBuffer
* @returns ArrayBuffer of stdout
*/
arrayBuffer(): ArrayBuffer;
/**
* Read stdout as Blob
* @returns Blob of stdout
*/
blob(): Blob;
}Usage Examples:
// Error handling with try-catch
try {
const result = await $`exit 1`;
} catch (error) {
if (error instanceof $.ShellError) {
console.log(`Command failed with exit code: ${error.exitCode}`);
console.log(`Stderr: ${error.stderr.toString()}`);
console.log(`Stdout: ${error.stdout.toString()}`);
}
}
// Conditional error handling
const result = await $`grep "pattern" file.txt`.nothrow();
if (result.exitCode === 0) {
console.log("Pattern found:", result.text());
} else if (result.exitCode === 1) {
console.log("Pattern not found");
} else {
console.error("Command failed:", result.stderr.toString());
}
// Complex shell pipelines with error handling
try {
const pipeline = await $`
find . -name "*.js" |
xargs grep -l "TODO" |
head -10
`.text();
const todoFiles = pipeline.trim().split("\n");
console.log(`Found TODO comments in ${todoFiles.length} files`);
} catch (error) {
if (error instanceof $.ShellError) {
console.error(`Pipeline failed: ${error.message}`);
}
}Complex shell operations including process substitution, background jobs, and integration with Node.js streams and processes.
// Process substitution and piping
type ShellExpression =
| Subprocess<SpawnOptions.Writable, SpawnOptions.Readable, SpawnOptions.Readable>
| ReadableStream
| WritableStream;Usage Examples:
// Process substitution with Bun.spawn
const subprocess = Bun.spawn(["echo", "Hello from subprocess"]);
const result = await $`cat ${subprocess.stdout}`;
console.log(result.text()); // "Hello from subprocess\n"
// Stream piping
const readable = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("line 1\nline 2\nline 3\n"));
controller.close();
}
});
const processed = await $`grep "2" ${readable}`.text();
console.log(processed); // "line 2\n"
// Complex data processing pipeline
const logAnalysis = await $`
tail -1000 /var/log/app.log |
grep ERROR |
awk '{print $1, $2}' |
sort |
uniq -c |
sort -nr |
head -5
`.text();
console.log("Top 5 error timestamps:");
console.log(logAnalysis);
// File processing with shell utilities
const files = await $`find . -name "*.json" -type f`.text();
const jsonFiles = files.trim().split("\n");
for (const file of jsonFiles) {
try {
const isValid = await $`python -m json.tool ${file}`.nothrow();
if (isValid.exitCode === 0) {
console.log(`✓ ${file} is valid JSON`);
} else {
console.log(`✗ ${file} has invalid JSON`);
}
} catch (error) {
console.log(`✗ ${file} could not be processed`);
}
}