はじめに
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行ずつ読み込み
次回は例外処理について学びます。