プログラミングでは、何らかのデータを読み出したり、書き出したりすることが数多くあります。
データの保存先は、データベースが用いられることが一般的ですが、まとまった文章や構造化されていないデータなどはファイルで保存されることも多くあります。
そのため、ファイル操作はプログラミングを学習する中で重要項目の1つとなります。
本記事では、JavaScriptでよく使われるファイル操作について解説します。
プログラミング初心者の方の学習や、忘れてしまった方の復習として、参考にしていただければ幸いです。
記載しているプログラムは、Node.js14.14.0を使って動作確認をしています。
JavaScriptのファイル操作
JavaScriptのファイル操作として、以下の内容を採り上げます。
- ファイル操作を扱うモジュール
- ファイルの読み込み
- ファイルの書き込み
- ファイルの削除
- ファイルの存在確認
- ファイルのコピー
- ディレクトリの一覧取得
- ディレクトリの作成
- ディレクトリの削除
ファイル操作を扱うモジュール
JavaScriptのファイル操作は、クライアントサイドでのファイル操作とサーバーサイドでファイル操作で大きく処理が異なります。
本記事では、Node.jsを使ったサーバーサイドでのファイル操作を対象とします。
Node.jsを使ったファイル操作でよく使われるのが fs モジュールです。
fsモジュールはNode.jsであれば標準で含まれているので、別途インストールする必要はありません。
利用したいjsファイルの中で、以下のように定義します。
const fs = require('fs');
fsモジュールを使ったファイル操作には、大きく同期方式と非同期方式があります。非同期方式は、さらにCallback方式とPromise方式があります。
本記事では、同期方式をメインに解説しますが、どちらを使うかは、要件や設計方針などで変わってきますので、ユースケースに応じた使い分けが必要となります。
ファイルの読み込み
ファイルを読み込むには、readFileSync を使います。
try { f = fs.readFileSync('./files/testfile.txt', 'utf-8'); console.log(f); } catch (err) { console.log(err); }
上記のプログラムでは、filesディレクトリのtestfile.txtファイルを読み込んで、内容をコンソールに出力しています。
ファイルが存在しないなどにより、読み込みに失敗した場合は、例外が発生し、catch文で例外情報をコンソールに出力されます。
この方法では、ファイルの中身を一度にすべて読みこんでしまうため、大きなファイルだと時間がかかったり、メモリを大量に消費してしまいます。
低コストで処理するために、readlineモジュールを使って、ファイルを1行ずつ読み込むこともできます。
const readline = require('readline'); rs = fs.createReadStream('./files/testfile.txta'); rl = readline.createInterface(rs, {}); rl.on('line', function(line) { console.log(line); });
ファイルの書き込み
ファイルの書き込みは、新規にファイルを作成して書き込む方法と、既存のファイルに追記する方法があります。
新規ファイルへの書き込み
新規にファイルを作成して書き込むには、writeFileSync を使います。
s = "新規にファイルを作成するテスト"; try { fs.writeFileSync('./files/writefile.txt', s, 'utf-8'); } catch (err) { console.log(err); }
上記のプログラムでは、filesディレクトリにwritefile.txtというファイルを作成し、その中に「新規にファイルを作成するテスト」という文字列を書き込んでいます。
このプログラムの実行後にファイルが作成されますが、毎回新規にファイルを作成するため、何度実行してもファイルの中身は同じ結果になります。
既存ファイルへの追記
既存のファイルへ追記するには、appendFileSyncを使います。
s = "既存ファイルに追記するテスト"; try { fs.appendFileSync('./files/writefile.txt', s, 'utf-8'); } catch (err) { console.log(err); }
上記のプログラムでは、filesディレクトリのwritefile.txtというファイルに、「既存ファイルに追記するテスト」という文字列を追記しています。
既存ファイルが存在しない場合は、新規にファイルを作成して書き込みます。
ファイルの削除
ファイルを削除するには、unlinkSyncを使います。
try { fs.unlinkSync('files/writefile.txt'); } catch (err) { console.log(err); }
上記のプログラムでは、filesディレクトリのwritefile.txtというファイルを削除します。
ファイルが存在しない場合は、例外が発生します。
ファイルの存在確認
ファイルが存在するかどうかを確認するには、existsSyncを使います。
console.log(fs.existsSync('./files/testfile.txt')); // -> true console.log(fs.existsSync('./files/notexists.txt')); // -> false console.log(fs.existsSync('./files')); // -> true
exsitsSyncは、ファイルが存在する場合はtrueを返し、存在しない場合はfalseを返します。
ディレクトリに対しても存在確認をすることができます。
ファイルかディレクトリかを判別するには、statsを取得して、isDirectory または isFile で確認します。
stats = fs.statSync('./files/testfile.txt'); console.log(stats.isFile()); // -> true stats = fs.statSync('./files'); console.log(stats.isDirectory()); // -> true
ファイルのコピー
ファイルをコピーするには、copyFileSyncを使います。
try { fs.copyFileSync('./files/testfile.txt', './files/copyfile.txt'); console.log(fs.existsSync('./files/copyfile.txt')); // -> true } catch (err) { console.log(err); }
上記のプログラムでは、filesディレクトリにcopyfile.txtを作成しますが、すでに同ファイルが存在している場合は、上書いてコピーします。
すでに同ファイルが存在しているときにエラーとしたい場合には、copyFileSyncにオプションを指定します。
try { fs.copyFileSync('./files/testfile.txt', './files/copyfile.txt', fs.constants.COPYFILE_EXCL); } catch (err) { console.log(err); }
ディレクトリの一覧取得
ディレクトリのファイル一覧を取得するには、readdirSyncを使います。
readdirSyncは、ファイル名やディレクトリ名を配列で返します。
try { d = fs.readdirSync('./files'); console.log(d); // -> [ 'copyfile.txt', 'testfile.txt' ] } catch (err) { console.log(err); }
ディレクトリの作成
ディレクトリを新規に作成するには、mkdirSyncを使います。
try { fs.mkdirSync('./files/testdir'); } catch (err) { console.log(err); }
上記のプログラムでは、filesディレクトリの配下にtestdirディレクトリを作成しています。
すでにtestdirディレクトリが存在する場合にはエラーとなります。
複数階層のディレクトリを一回で作成するには、recursiveオプションを付けます。
recursiveオプションをつけると、すでにディレクトリが存在している場合でもエラーになりません。
try { fs.mkdirSync('./files/testdir2/testdir2', {recursive: true}); } catch (err) { console.log(err); }
プログラムの中では、一時的にファイルを扱うためにディレクトリを作成したい場合があります。
プログラムの特性にもよりますが、基本的には他のプログラムや処理で使うディレクトリとは異なる専用のディレクトリを作るのが一般的です。
そのような用途の場合に、ランダムな名称を付けてくれるmkdtempSyncがあります。
try { d = fs.mkdtempSync('./files/dir-'); console.log(d); // -> ./files/dir-AYwbsV } catch (err) { console.log(err); }
mkdtempSyncは、OSによって異なる場合もありますが、6文字のランダムな文字列を末尾に付加してディレクトリを作成します。
作成したディレクトリ名が戻り値として返されます。
ディレクトリの削除
ディレクトリを削除するには、rmdirSyncを使います。
try { fs.rmdirSync('files/testdir'); } catch (err) { console.log(err); }
削除対象のディレクトリに、ファイルやディレクトリが存在する場合はエラーとなります。
ディレクトリのみ存在する状態であれば、recursiveオプションで削除可能です。recursiveオプションを指定した場合は、削除対象が存在しなくてもエラーにはなりません。
ディレクトリとファイルを一括で削除したい場合は、rmを使います。
rmはNode.js 14.14.0から追加されています。
try { fs.rmSync('./files/testdir3', {recursive: true}); } catch (err) { console.log(err); }
上記のプログラムでは、recursiveオプションを有効にすることで、testdir3ディレクトリを配下のファイルやディレクトリとともに一括で削除します。
複数の処理で1つのファイル操作を行う場合は要注意
ファイルはデータを保存しておく場所として使いやすいのですが、複数のプロセスやスレッドなどから同じファイルを操作しようとすると思わぬことが発生することがあるので注意が必要です。
僕がこれまでに携わったプロジェクトでも以下のようなことがありました。
- 複数のプロセスから同一のログファイルに書き込んだらファイルが壊れた
- 参照しようとしたら別の処理が先に消していた
- あるサーバが作成したファイルを別のサーバから参照しようとしたら、存在するはずのファイルが見えない状態だった
これらは、設計ミスやOSのファイル管理方法を理解出来ていないことが原因で発生したものでした。
ファイルはOSと密接に関わっているため、ローカル環境では上手く動いても、別の環境では意図通りに動かないといったことがあります。
ファイルを扱う際は、設計時点で問題ないかをきちんと議論することをおすすめします。
今回はJavaScriptのファイル操作について解説しました。
以上、参考になれば幸いです。
コメント