[Node.js] ファイルに書き込む様々な方法

Node.jsでは、ファイルにデータを書き込む様々な方法が用意されています。今回はよく使われる方法についてまとめてみます。

非同期

書き込み (fs.writeFile)

ファイルが存在しない場合は新規に作成し、ファイルが存在する場合は内容をすべて削除して書き込みます。

const fs = require("fs");

// 書き込むデータ準備
const data = "Hello Node";

// 書き込み
fs.writeFile("file1.txt", data, (err) => {
  if (err) throw err;
  console.log('正常に書き込みが完了しました');
});

バイナリ

サンプルでは文字列を書き込んでいますが、バイナリデータも可能です。定数dataを定義する行を書き換えてみてください。

const data = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);

モードを指定

またファイルを開く際のモードはオプションで指定できます。以下でaモード(追加書き込みモード)としてファイルを開きます。

const options = {
  flag: 'a'
};

fs.writeFile("file1.txt", data, options, (err) => {
  if (err) throw err;
  console.log('正常に書き込みが完了しました');
});

モードの一覧はこのページの後半「ファイルを開く際のモード一覧」にまとめてあります。

追加書き込み (fs.appendFile)

先ほどのfs.writeFile()aモードを指定すればよいのですが、ここでは別の方法もご紹介しておきます。

const fs = require("fs");
const data = "Nice to meet you\n";

fs.appendFile("file1.txt", data, (err) => {
  if (err) throw err;
  console.log("正常に書き込みが完了しました");
});

指定した場所に書き込む (fs.write)

以下のようなテキストファイルがあったとします。このNodeの場所をWorldに書き換える処理を書いてみます。

Hello Node

fs.write()の第3引数に書き込み始める先頭からのバイト数を指定することが可能です。

const fs = require("fs");

// 書き込み始める先頭からのバイト数
const POSITION = 6;

fs.open("file1.txt", "a", (err, fd) => {
  if (err) throw err;

  // 場所を第3引数に指定する
  fs.write(fd, "World", POSITION, (err, written, string)=>{
    if (err) throw err;

    console.log(`正常に書き込みが完了しました (${written}bytes)`);

    fs.close(fd, (err) => {
      if (err) throw err;
    });
  });
});

実行すると置換が成功しました。

Hello World

複数回に分けて書き込む (Stream)

fs.write()fs.writeFile()で何度も書き込もうとするとCallback地獄に突き落とされるわけですが、そんなときに重宝するのがStream。

const fs = require("fs");

const stream = fs.createWriteStream("file2.txt");
stream.write("Hello, ");
stream.write("Stream");
stream.end("\n");

// エラー処理
stream.on("error", (err)=>{
  if(err)
    console.log(err.message);
});

stream.write()を必要なだけ実行し、最後にstream.end()を呼び出します。それ以降にstream.write()すると実行時エラーとなるので注意が必要です。エラー処理はerrorイベントを予め設定しておくだけです。

モードを指定

fs.createWriteStream()する際にモードを指定できます。デフォルトでは新規書き込みモード(w)になっています。

const options = {
  flags: "a"  // 追加書き込みモード
};

const stream = fs.createWriteStream("file2.txt", options);

モードの一覧はこのページの後半「ファイルを開く際のモード一覧」にまとめてあります。

文字コードを指定

文字コードはfs.createWriteStreamでStreamを作成する際にまとめて指定することができます。

const options = {
  encoding: "utf8"
};

const stream = fs.createWriteStream("file2.txt", options);

もしくはstream.write()の第2引数でも指定可能です。

const stream = fs.createWriteStream("file2.txt");
stream.write("HEY", "utf8");
stream.end("\n");

バッファリング

ファイルに細かく出力すると効率が悪い場合、ある程度メモリ上に蓄えてからまとめてファイルへ書き込む「バッファリング」も簡単に対応できます。

Stream作成後に、事前にstream.cork()を呼んでおくとそれ以降はメモリ上にバッファリングされ、stream.uncork()stream.end()が呼ばれるとファイルに書き出しが行われます。

次のサンプルは1秒置きにStreamへ出力しますが、実際にファイルへ書き込まれるのは3回に1回(3秒に1回)です。

const fs = require("fs");

const stream = fs.createWriteStream("file3.txt");
stream.cork();  // ファイルに書き込まずバッファリングする

//---------------------------------
// 3秒置きにStreamに出力
//---------------------------------
let count = 100;
let timer = setInterval( () => {
  // バッファに出力
  stream.write(`${count}\n`);
  console.log(count);

  // 3回に1回ファイルに出力
  if( (count % 300) === 0){
    stream.uncork();    // ファイルへ出力する
    stream.cork();      // 再度バッファリングする
    console.log("-- uncork()");
  }

  // 10回目でタイマー終了
  if( count >= 1000 ){
    stream.end("\n");     //ここでもファイルへ出力される
    clearInterval(timer);
    console.log("-- end()");
  }

  // カウンターを加算
  count += 100;
}, 1000);

stream.on("error", (err)=>{
  if(err)
    console.log(err.message);
});

確認方法としてはTerminalを2つ開き、片方でNodeを実行、もう片方でtail -fなどすれば良いでしょう。

$ node stream.js
$ tail -f file3.txt

同期

Syncシリーズ

書き込み (fs.writeFileSync)

const fs = require("fs");

try{
  fs.writeFileSync("file5.txt", "Hello Node");
}
catch(e){
  console.log(e.message);
}

第3引数でモードも指定できます。デフォルトはwです。

fs.writeFileSync("file5.txt", "Hello Node", {flag: "a"});

追加書き込み (fs.appendFileSync)

const fs = require("fs");

try{
  fs.appendFileSync("file5.txt", "Hello Node");
}
catch(e){
  console.log(e.message);
}

第3引数でモードも指定できます。デフォルトはaです。

fs.writeFileSync("file5.txt", "Hello Node", {flag: "ax"});

複数回に分けて書き込み(fs.writeSync)

const fs = require("fs");

try {
  const fd = fs.openSync("file5.txt", "w");
  fs.writeSync(fd, "Hello ");
  fs.writeSync(fd, "Node");
  fs.writeSync(fd, "\n");
  fs.closeSync(fd);
}
catch(e){
  console.log(e.message);
}

書き込む際に第3引数に整数を渡すと、書き込み始める場所を指定することができます。

fs.writeSync(fd, "Hello ", 10);

Promise

書き込み (fs.writeFile)

const fs = require("fs").promises;

const writer = async (file) =>{
  try{
    await fs.writeFile(file, "Hello Node with Promise");
  }
  catch(e){
    console.log(e.message);
  }
};

writer("file6.txt");

こちらも同様に第3引数でモードが指定できます。デフォルトはwです。

await fs.writeFile(file, "Hello Node", {flag: "a"});

追加書き込み (fs.appendFile)

const fs = require("fs").promises;

const writer = async (file) =>{
  try{
    await fs.appendFile(file, "Hello Node ");
    await fs.appendFile(file, "with Promise\n");
  }
  catch(e){
    console.log(e.message);
  }
};

writer("file7.txt");

おなじみ第3引数でモードが指定できます。デフォルトはaです。

await fs.appendFile(file, "Hello Node", {flag: "ax"});

その他

ファイルを開く際のモード一覧

意訳してますので、正確なところは公式ドキュメントの「File System Flags」の項目を参照ください。

読み取りモード

モード 概要
r 読み取りモードで開きます。指定パスにファイルが存在しない場合は例外が発生します。
r+ rと同様に読み取りモードで開きますが、書き込みも可能です。もし指定パスにファイルが存在しない場合は例外が発生します。
rs+ r+と同様の挙動ですが同期的なモードで開きます。OSのファイルキャッシュは利用しません。

rs+fs.open()などを同期的なモードに変更するわけではない点に注意です。同期的な処理をする場合はfs.openSync()などを利用します。

書き込みモード

モード 概要
w 書き込みモードで開きます。指定パスにファイルが存在しない場合は新規にファイルが作成され、もし存在する場合は内容をすべて削除した上で書き込みます。
wx wモードと同様に書き込みモードで開きますが、もし指定パスにファイルが存在する場合はエラーになります。
w+ wモードと同様の挙動になりますが、読み込みを行うことも可能です。
wx+ w+モードと同様の共同になりますが、もし指定パスにファイルが存在する場合はエラーになります。

個人的に勘違いしていたのですがwx, wx+は同名のファイルが存在する場合にエラーとなります。存在しない場合に書き込みができます。誤って内容を消さないようにという親切機能ですね。

追加書き込みモード

モード 概要
a 追加書き込みモードで開きます。指定パスにファイルが存在しない場合は新規にファイルが作成されます。
ax aと同様に追加書き込みモードで開きますが、もし指定パスにファイルが存在する場合はエラーになります。
a+ aと同様の挙動になりますが、読み込みを行うことも可能です。
ax+ a+と同様の挙動になりますが、もし指定パスにファイルが存在する場合はエラーになります。
as 同期的な追加書き込みモードで開きます。指定パスにファイルが存在しない場合は新規にファイルが作成されます。
as+ asと同様の挙動になりますが、読み込みを行うことも可能です。

こちらもax, ax+は同名のファイルが存在する場合にエラーとなります。存在しない場合にのみ追加書き込みができます。追加書き込みモードであればwほど致命的な症状にはならない場合が多いですが、保険はかけておいて損はないですね。

関連ページ

blog.katsubemakito.net

参考ページ

ハンズオンNode.js

ハンズオンNode.js

Amazon