ファイルを読み込む

前のセクションではコマンドライン引数からファイルパスを取得して利用できるようになりました。 このセクションでは渡されたファイルパスを元にMarkdownファイルを読み込んで、標準出力に表示してみましょう。

fsモジュールを使ってファイルを読み込む

前のセクションで取得できるようになったファイルパスを元に、ファイルを読み込みましょう。 Node.jsでファイルの読み書きを行うには、標準モジュールのfsモジュールを使います。 まずは読み込む対象のファイルを作成しましょう。sample.mdという名前でmain.jsと同じnodecliディレクトリに配置します。

sample.md

# sample

fsモジュール

fsモジュールは、Node.jsでファイルの読み書きを行うための基本的な関数を提供するモジュールです。 fsモジュールのメソッドとして非同期形式と同期形式の両方が提供されています。

非同期形式の関数は常にコールバック関数を受け取ります。 コールバック関数の第一引数は必ずその処理で発生したエラーオブジェクトになり、残りの引数は処理の返り値となります。 処理が成功したときには、第一引数はnullまたはundefinedになります。 一方、同期形式の関数が処理に失敗したときは例外を発生させるので、try...catch構文によって例外処理を行えます。

次のサンプルコードは、指定したファイルを読み込む非同期形式のreadFileメソッドの例です。

const fs = require("fs");

fs.readFile("sample.md", (err, file) => {
    if (err) {
        console.error(err);
    } else {
        console.log(file);
    }
});

そして、次のサンプルコードは、同じく指定したファイルを読み込む同期形式readFileSyncメソッドの例です。

const fs = require("fs");

try {
    const file = fs.readFileSync("sample.md");
} catch (err) {
    // ファイルが読み込めないなどのエラーが発生したときに呼ばれる
}

Node.jsはシングルスレッドなので、他の処理をブロックしにくい非同期形式のAPIを選ぶことがほとんどです。 Node.jsにはfsモジュール以外にも多くの非同期APIがあるので、非同期処理に慣れておきましょう。

readFile関数を使う

それではfsモジュールのreadFileメソッドを使ってsample.mdファイルを読み込んでみましょう。 次のようにmain.jsを変更し、コマンドライン引数から取得したファイルパスを元にファイルを読み込んでコンソールに出力します。

main.js

const program = require("commander");
// fsモジュールをfsオブジェクトとしてインポートする
const fs = require("fs");

// コマンドライン引数からファイルパスを取得する
program.parse(process.argv);
const filePath = program.args[0];

// ファイルを非同期で読み込む
fs.readFile(filePath, (err, file) => {
    console.log(file);
});

sample.mdを引数に渡した実行結果は次のようになります。 文字列になっていないのは、コールバック関数の第二引数はファイルの中身を表すBufferインスタンスだからです。 Bufferインスタンスはファイルの中身をバイト列として保持しています。 そのため、そのままconsole.logメソッドに渡しても人間が読める文字列にはなりません。

$ node main.js sample.md
<Buffer 23 20 73 61 6d 70 6c 65>

fs.readFile関数は引数によってファイルの読み込み方を指定できます。 ファイルのエンコードを第二引数であらかじめ指定しておけば、自動的に文字列に変換された状態でコールバック関数に渡されます。 次のようにmain.jsを変更し、読み込まれるファイルをUTF-8として変換させます。

main.js

const program = require("commander");
const fs = require("fs");

program.parse(process.argv);
const filePath = program.args[0];

// ファイルをUTF-8として非同期で読み込む
fs.readFile(filePath, { encoding: "utf8" }, (err, file) => {
    console.log(file);
});

先ほどと同じコマンドをもう一度実行すると、実行結果は次のようになります。 sample.mdファイルの中身を文字列として出力できました。

$ node main.js sample.md
# sample

エラーハンドリング

先ほどの例では触れませんでしたが、fsモジュールのコールバック関数の第一引数には常にエラーオブジェクトが渡されます。 ファイルの読み書きは存在の有無や権限、ファイルシステムの違いなどによって例外が発生しやすいので、必ずエラーハンドリング処理を書きましょう。

次のようにmain.jsを変更し、errオブジェクトがnullまたはundefinedではないことだけをチェックするシンプルなエラーハンドリングです。 エラーが発生していたときにはエラーメッセージを表示し、process.exit関数に終了ステータスを指定してプロセスを終了しています。 ここでは、一般的なエラーを表す終了ステータスの1でプロセスを終了しています。

main.js

const program = require("commander");
const fs = require("fs");

program.parse(process.argv);
const filePath = program.args[0];

// ファイルを非同期で読み込む
fs.readFile(filePath, { encoding: "utf8" }, (err, file) => {
    if (err) {
        console.error(err.message);
        // 終了ステータス 1(一般的なエラー)としてプロセスを終了する
        process.exit(1);
        return;
    }
    console.log(file);
});

存在しないファイルであるnotfound.mdをコマンドライン引数に渡して実行すると、次のようにエラーが発生して終了します。

$ node main.js notfound.md
ENOENT: no such file or directory, open 'notfound.md'

これでコマンドライン引数に指定したファイルを読み込んで標準出力に表示できました。 次のセクションでは読み込んだMarkdownファイルをHTMLに変換する処理を追加していきます。

このセクションのチェックリスト

  • fsモジュールのreadFile関数を使ってファイルを読み込んだ
  • UTF-8形式のファイルの中身をコンソールに出力した
  • readFile関数の呼び出しにエラーハンドリング処理を記述した