チュートリアル

JavaScript実践編:ファイル操作

JavaScriptNode.jsファイル操作実践
広告エリア

はじめに

JavaScript実践編では、Node.js環境での実際の開発スキルを学びます。第1回はファイル操作です。

Node.jsではfsモジュールを使ってファイル操作を行います。

fsモジュールの基本

Node.jsには3種類のファイル操作APIがあります。

// 1. 同期API(ブロッキング)
const fs = require('fs');

// 2. コールバックAPI(非同期)
const fs = require('fs');

// 3. Promise API(推奨)
const fs = require('fs').promises;
// または
const fs = require('fs/promises');

この記事ではPromise APIを中心に解説します。

テキストファイルの読み書き

ファイルを読む

const fs = require('fs/promises');

// ファイル全体を読み込む
async function readFile() {
  const content = await fs.readFile('sample.txt', 'utf-8');
  console.log(content);
}

// 1行ずつ処理する
const readline = require('readline');
const fsSync = require('fs');

async function readLines() {
  const stream = fsSync.createReadStream('sample.txt', 'utf-8');
  const rl = readline.createInterface({ input: stream });

  for await (const line of rl) {
    console.log(line);
  }
}

ファイルに書き込む

const fs = require('fs/promises');

// 新規作成・上書き
async function writeFile() {
  await fs.writeFile('output.txt', 'こんにちは\nJavaScript\n', 'utf-8');
}

// 追記
async function appendFile() {
  await fs.appendFile('output.txt', '追加の行\n', 'utf-8');
}

// 複数行を書き込み
async function writeLines() {
  const lines = ['1行目', '2行目', '3行目'];
  await fs.writeFile('output.txt', lines.join('\n') + '\n', 'utf-8');
}

同期APIと非同期API

const fs = require('fs');
const fsPromises = require('fs/promises');

// 同期API(シンプルだがブロッキング)
const content = fs.readFileSync('file.txt', 'utf-8');
console.log(content);

// 非同期API(推奨)
async function read() {
  const content = await fsPromises.readFile('file.txt', 'utf-8');
  console.log(content);
}

// コールバックAPI(レガシー)
fs.readFile('file.txt', 'utf-8', (err, content) => {
  if (err) throw err;
  console.log(content);
});

ファイル操作オプション

const fs = require('fs/promises');

// フラグを指定
await fs.writeFile('file.txt', 'content', {
  encoding: 'utf-8',
  flag: 'w'  // 書き込みモード
});

// 主なフラグ
// 'r'  - 読み込み(デフォルト)
// 'w'  - 書き込み(上書き)
// 'a'  - 追記
// 'wx' - 新規作成(既存ファイルがあるとエラー)

JSONファイルの操作

const fs = require('fs/promises');

// JSONを読み込む
async function readJson() {
  const content = await fs.readFile('data.json', 'utf-8');
  const data = JSON.parse(content);
  console.log(data);
  return data;
}

// JSONに書き込む
async function writeJson() {
  const data = {
    name: '太郎',
    age: 25,
    skills: ['JavaScript', 'TypeScript']
  };

  await fs.writeFile(
    'output.json',
    JSON.stringify(data, null, 2),
    'utf-8'
  );
}

// JSONの読み書きを簡潔に
async function updateJson() {
  // 読み込み
  const data = JSON.parse(await fs.readFile('config.json', 'utf-8'));

  // 更新
  data.version = '2.0.0';

  // 書き込み
  await fs.writeFile('config.json', JSON.stringify(data, null, 2));
}

CSVファイルの操作

シンプルなCSV処理は自前で、複雑な場合はcsv-parseなどのライブラリを使用します。

const fs = require('fs/promises');

// シンプルなCSV読み込み
async function readCsv() {
  const content = await fs.readFile('data.csv', 'utf-8');
  const lines = content.trim().split('\n');
  const headers = lines[0].split(',');

  const data = lines.slice(1).map(line => {
    const values = line.split(',');
    return headers.reduce((obj, header, i) => {
      obj[header] = values[i];
      return obj;
    }, {});
  });

  return data;
}

// CSVに書き込む
async function writeCsv() {
  const data = [
    { name: '太郎', age: 25, job: 'エンジニア' },
    { name: '花子', age: 22, job: 'デザイナー' }
  ];

  const headers = Object.keys(data[0]);
  const lines = [
    headers.join(','),
    ...data.map(row => headers.map(h => row[h]).join(','))
  ];

  await fs.writeFile('output.csv', lines.join('\n'), 'utf-8');
}

パス操作(pathモジュール)

const path = require('path');

// パスの結合
const filePath = path.join('documents', 'report.txt');
console.log(filePath);  // documents/report.txt (OS依存)

// パス情報の取得
const p = '/home/user/documents/report.txt';
console.log(path.basename(p));  // report.txt
console.log(path.dirname(p));   // /home/user/documents
console.log(path.extname(p));   // .txt
console.log(path.parse(p));
// { root: '/', dir: '/home/user/documents',
//   base: 'report.txt', ext: '.txt', name: 'report' }

// 絶対パスに変換
console.log(path.resolve('file.txt'));

// パスの正規化
console.log(path.normalize('/foo/bar//baz/..'));  // /foo/bar

ディレクトリ操作

const fs = require('fs/promises');
const path = require('path');

// ファイル・ディレクトリの存在確認
async function exists(filePath) {
  try {
    await fs.access(filePath);
    return true;
  } catch {
    return false;
  }
}

// ファイル情報の取得
async function getStats() {
  const stats = await fs.stat('file.txt');
  console.log(stats.isFile());      // true
  console.log(stats.isDirectory()); // false
  console.log(stats.size);          // バイト数
}

// ディレクトリ作成
await fs.mkdir('new_folder');
await fs.mkdir('a/b/c', { recursive: true });

// ディレクトリ内のファイル一覧
const files = await fs.readdir('.');
console.log(files);

// 詳細情報付き
const entries = await fs.readdir('.', { withFileTypes: true });
for (const entry of entries) {
  console.log(entry.name, entry.isDirectory() ? 'DIR' : 'FILE');
}

// ファイル削除
await fs.unlink('file.txt');

// ディレクトリ削除
await fs.rmdir('empty_folder');
await fs.rm('folder', { recursive: true });  // 中身ごと削除

// ファイルのコピー
await fs.copyFile('src.txt', 'dst.txt');

// ファイルの移動・リネーム
await fs.rename('old.txt', 'new.txt');

ストリームによる大容量ファイル処理

大きなファイルはストリームで処理します。

const fs = require('fs');
const { pipeline } = require('stream/promises');

// ファイルのコピー(ストリーム)
async function copyLargeFile(src, dst) {
  await pipeline(
    fs.createReadStream(src),
    fs.createWriteStream(dst)
  );
}

// 行ごとの処理(大容量ファイル対応)
const readline = require('readline');

async function processLargeFile(filePath) {
  const stream = fs.createReadStream(filePath, 'utf-8');
  const rl = readline.createInterface({ input: stream });

  let lineCount = 0;
  for await (const line of rl) {
    lineCount++;
    // 各行の処理
  }
  return lineCount;
}

実践例:ログファイル処理

const fs = require('fs/promises');
const path = require('path');
const readline = require('readline');
const fsSync = require('fs');

async function parseLogFile(logPath) {
  const errors = [];
  const stream = fsSync.createReadStream(logPath, 'utf-8');
  const rl = readline.createInterface({ input: stream });

  let lineNum = 0;
  for await (const line of rl) {
    lineNum++;
    if (line.includes('ERROR')) {
      errors.push({
        line: lineNum,
        content: line.trim()
      });
    }
  }

  return errors;
}

async function saveErrorReport(errors, outputPath) {
  const lines = [
    `エラーレポート - ${new Date().toISOString()}`,
    '='.repeat(50),
    '',
    ...errors.map(e => `行 ${e.line}: ${e.content}`),
    '',
    `合計: ${errors.length}件のエラー`
  ];

  await fs.writeFile(outputPath, lines.join('\n'), 'utf-8');
}

// 使用例
async function main() {
  const errors = await parseLogFile('app.log');
  await saveErrorReport(errors, 'error_report.txt');
}

まとめ

  • fs/promisesで非同期ファイル操作
  • JSON.parse/JSON.stringifyでJSONを扱う
  • pathモジュールでパス操作
  • 大容量ファイルはストリームで処理
  • readlineで1行ずつ読み込み

次回は例外処理について学びます。

広告エリア