Bun 文件操作 #

概述 #

Bun 提供了简洁高效的文件操作 API,包括 Bun.file()Bun.write() 和文件监控功能。这些 API 基于 Web 标准设计,使用 Promise 进行异步操作。

Bun.file() #

创建文件引用 #

typescript
// 创建文件引用
const file = Bun.file("./data.txt");

// 从路径创建
const file2 = Bun.file("/absolute/path/to/file.txt");

// 从相对路径创建
const file3 = Bun.file(import.meta.dir + "/data.txt");

文件属性 #

typescript
const file = Bun.file("./data.txt");

// 文件信息
console.log(file.size);          // 文件大小(字节)
console.log(file.type);          // MIME 类型
console.log(file.name);          // 文件名
console.log(file.lastModified);  // 最后修改时间

// 检查文件是否存在
const exists = await file.exists();
console.log("File exists:", exists);

读取文件内容 #

typescript
const file = Bun.file("./data.txt");

// 读取为文本
const text = await file.text();
console.log(text);

// 读取为 JSON
const jsonFile = Bun.file("./data.json");
const data = await jsonFile.json();
console.log(data);

// 读取为 ArrayBuffer
const buffer = await file.arrayBuffer();

// 读取为 Blob
const blob = file.slice(0, 1024);

// 读取为流
const stream = file.stream();
const reader = stream.getReader();

文件切片 #

typescript
const file = Bun.file("./large-file.bin");

// 读取部分内容
const chunk1 = file.slice(0, 1024);      // 前 1KB
const chunk2 = file.slice(1024, 2048);   // 第二个 1KB
const chunk3 = file.slice(1024);         // 从 1KB 到末尾

// 读取切片内容
const text1 = await chunk1.text();

处理不同文件类型 #

typescript
// 文本文件
const textFile = Bun.file("./readme.txt");
const text = await textFile.text();

// JSON 文件
const jsonFile = Bun.file("./config.json");
const config = await jsonFile.json();

// 图片文件
const imageFile = Bun.file("./image.png");
const imageBuffer = await imageFile.arrayBuffer();

// 大文件流式读取
const largeFile = Bun.file("./video.mp4");
const stream = largeFile.stream();

Bun.write() #

写入文本 #

typescript
// 写入文本
await Bun.write("./output.txt", "Hello, Bun!");

// 覆盖写入
await Bun.write("./output.txt", "New content");

写入 JSON #

typescript
// 写入 JSON 对象
await Bun.write("./data.json", { name: "Bun", version: 1.2 });

// 格式化 JSON
const data = { name: "Bun", features: ["fast", "simple"] };
await Bun.write("./data.json", JSON.stringify(data, null, 2));

写入二进制数据 #

typescript
// 写入 ArrayBuffer
const buffer = new TextEncoder().encode("Binary data");
await Bun.write("./binary.bin", buffer);

// 写入 Uint8Array
const uint8Array = new Uint8Array([72, 101, 108, 108, 111]);
await Bun.write("./hello.bin", uint8Array);

写入 Blob #

typescript
// 写入 Blob
const blob = new Blob(["Hello, Blob!"], { type: "text/plain" });
await Bun.write("./blob.txt", blob);

// 从 Blob 创建文件
const imageBlob = new Blob([imageData], { type: "image/png" });
await Bun.write("./image.png", imageBlob);

复制文件 #

typescript
// 复制文件
const source = Bun.file("./source.txt");
await Bun.write("./dest.txt", source);

// 复制大文件
const largeFile = Bun.file("./large.mp4");
await Bun.write("./copy.mp4", largeFile);

写入选项 #

typescript
// 创建目录(如果不存在)
await Bun.write("./new-dir/file.txt", "content");

// 设置权限(Unix)
await Bun.write("./script.sh", "#!/bin/bash\necho hello", {
  createPath: true,
});

目录操作 #

创建目录 #

typescript
import { mkdir } from "fs/promises";

// 创建目录
await mkdir("./new-dir");

// 递归创建
await mkdir("./deep/nested/dirs", { recursive: true });

读取目录 #

typescript
import { readdir } from "fs/promises";

// 读取目录
const files = await readdir("./src");
console.log(files);

// 递归读取
const allFiles = await readdir("./src", { recursive: true });
console.log(allFiles);

// 带文件类型
import { readdirSync, Dirent } from "fs";

const entries = readdirSync("./src", { withFileTypes: true });
for (const entry of entries as Dirent[]) {
  console.log(`${entry.name} - ${entry.isDirectory() ? "DIR" : "FILE"}`);
}

删除目录 #

typescript
import { rm, rmdir } from "fs/promises";

// 删除空目录
await rmdir("./empty-dir");

// 递归删除
await rm("./dir", { recursive: true, force: true });

遍历目录 #

typescript
import { readdir, stat } from "fs/promises";
import { join } from "path";

async function walkDir(dir: string): Promise<string[]> {
  const files: string[] = [];
  const entries = await readdir(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = join(dir, entry.name);
    if (entry.isDirectory()) {
      files.push(...await walkDir(fullPath));
    } else {
      files.push(fullPath);
    }
  }
  
  return files;
}

const allFiles = await walkDir("./src");
console.log(allFiles);

文件信息 #

获取文件状态 #

typescript
import { stat, lstat } from "fs/promises";

// 获取文件状态
const stats = await stat("./file.txt");

console.log(stats.isFile());       // 是否是文件
console.log(stats.isDirectory());  // 是否是目录
console.log(stats.size);           // 文件大小
console.log(stats.mtime);          // 修改时间
console.log(stats.atime);          // 访问时间
console.log(stats.ctime);          // 创建时间
console.log(stats.mode);           // 权限模式

// 符号链接状态
const linkStats = await lstat("./symlink");
console.log(linkStats.isSymbolicLink());

检查文件存在 #

typescript
import { access, constants } from "fs/promises";

// 检查文件是否存在
try {
  await access("./file.txt", constants.F_OK);
  console.log("File exists");
} catch {
  console.log("File does not exist");
}

// 检查读写权限
try {
  await access("./file.txt", constants.R_OK | constants.W_OK);
  console.log("File is readable and writable");
} catch {
  console.log("File is not accessible");
}

// 使用 Bun.file
const file = Bun.file("./file.txt");
const exists = await file.exists();

文件监控 #

Bun.watch() #

typescript
// 监控文件变化
const watcher = Bun.watch("./src", {
  recursive: true,
});

watcher.on("change", (event, filename) => {
  console.log(`File changed: ${filename}`);
  console.log(`Event: ${event}`);  // "change", "create", "delete"
});

watcher.on("error", (error) => {
  console.error("Watch error:", error);
});

// 关闭监控
watcher.close();

监控特定文件 #

typescript
const watcher = Bun.watch("./config.json");

watcher.on("change", async (event, filename) => {
  console.log(`${filename} was ${event}`);
  
  // 重新加载配置
  const config = await Bun.file("./config.json").json();
  console.log("New config:", config);
});

使用 fs.watch #

typescript
import { watch } from "fs";

const watcher = watch("./src", { recursive: true }, (event, filename) => {
  console.log(`${filename} - ${event}`);
});

watcher.on("error", (error) => {
  console.error("Watch error:", error);
});

// 关闭
watcher.close();

流式处理 #

读取流 #

typescript
const file = Bun.file("./large-file.txt");
const stream = file.stream();

const reader = stream.getReader();
let result = "";

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  result += new TextDecoder().decode(value);
}

console.log(result);

写入流 #

typescript
import { createWriteStream } from "fs";

const writeStream = createWriteStream("./output.txt");

writeStream.write("Line 1\n");
writeStream.write("Line 2\n");
writeStream.write("Line 3\n");

writeStream.end();

管道流 #

typescript
import { createReadStream, createWriteStream } from "fs";
import { pipeline } from "stream/promises";

await pipeline(
  createReadStream("./source.txt"),
  createWriteStream("./dest.txt")
);

console.log("File copied");

Transform 流 #

typescript
import { Transform, pipeline } from "stream";

const upperCase = new Transform({
  transform(chunk, encoding, callback) {
    callback(null, chunk.toString().toUpperCase());
  },
});

await pipeline(
  createReadStream("./input.txt"),
  upperCase,
  createWriteStream("./output.txt")
);

临时文件 #

创建临时文件 #

typescript
import { mkdtemp, writeFile } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";

// 创建临时目录
const tempDir = await mkdtemp(join(tmpdir(), "bun-"));
console.log("Temp dir:", tempDir);

// 创建临时文件
const tempFile = join(tempDir, "temp.txt");
await writeFile(tempFile, "Temporary content");

// 读取
const content = await Bun.file(tempFile).text();
console.log(content);

// 清理
import { rm } from "fs/promises";
await rm(tempDir, { recursive: true });

文件权限 #

修改权限 #

typescript
import { chmod, chown } from "fs/promises";

// 修改权限
await chmod("./script.sh", 0o755);  // rwxr-xr-x

// 修改所有者
await chown("./file.txt", 1000, 1000);  // uid, gid

符号链接 #

typescript
import { symlink, readlink, unlink } from "fs/promises";

// 创建符号链接
await symlink("./target.txt", "./link.txt");

// 读取链接目标
const target = await readlink("./link.txt");
console.log("Link target:", target);

// 删除链接
await unlink("./link.txt");

实用工具函数 #

递归复制目录 #

typescript
import { cp, mkdir } from "fs/promises";
import { dirname } from "path";

async function copyDir(src: string, dest: string) {
  await mkdir(dest, { recursive: true });
  await cp(src, dest, { recursive: true });
}

await copyDir("./src", "./backup");

查找文件 #

typescript
import { readdir, stat } from "fs/promises";
import { join, extname } from "path";

async function findFiles(
  dir: string,
  predicate: (name: string) => boolean
): Promise<string[]> {
  const results: string[] = [];
  const entries = await readdir(dir, { withFileTypes: true });
  
  for (const entry of entries) {
    const fullPath = join(dir, entry.name);
    if (entry.isDirectory()) {
      results.push(...await findFiles(fullPath, predicate));
    } else if (predicate(entry.name)) {
      results.push(fullPath);
    }
  }
  
  return results;
}

// 查找所有 TypeScript 文件
const tsFiles = await findFiles("./src", (name) => name.endsWith(".ts"));
console.log(tsFiles);

文件大小格式化 #

typescript
function formatSize(bytes: number): string {
  const units = ["B", "KB", "MB", "GB", "TB"];
  let size = bytes;
  let unitIndex = 0;
  
  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024;
    unitIndex++;
  }
  
  return `${size.toFixed(2)} ${units[unitIndex]}`;
}

const stats = await stat("./large-file.bin");
console.log(`Size: ${formatSize(stats.size)}`);

最佳实践 #

错误处理 #

typescript
async function safeReadFile(path: string): Promise<string | null> {
  try {
    const file = Bun.file(path);
    if (await file.exists()) {
      return await file.text();
    }
    return null;
  } catch (error) {
    console.error(`Error reading ${path}:`, error);
    return null;
  }
}

并发文件操作 #

typescript
// 并发读取多个文件
const files = ["a.txt", "b.txt", "c.txt"];
const contents = await Promise.all(
  files.map((f) => Bun.file(f).text())
);
console.log(contents);

流式处理大文件 #

typescript
async function processLargeFile(path: string) {
  const file = Bun.file(path);
  const stream = file.stream();
  const reader = stream.getReader();
  const decoder = new TextDecoder();
  
  let lineBuffer = "";
  
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    
    lineBuffer += decoder.decode(value, { stream: true });
    const lines = lineBuffer.split("\n");
    lineBuffer = lines.pop() || "";
    
    for (const line of lines) {
      // 处理每一行
      console.log(line);
    }
  }
}

下一步 #

现在你已经了解了 Bun 的文件操作,接下来学习 SQLite 数据库 深入了解 Bun 的内置数据库功能。

最后更新:2026-03-29