Node.jsでローカルファイルを軽やかに操作するスクリプト集

# はじめに


最近、50GBのアクセスログファイルを解析する機会があったのですが、データサイズが大きすぎて普通のファイルシステムでは途中で処理が落ちてしまうんですね。
そこで**Node.js**の非同期処理（ストリーム）を採用したところなんの問題もなくサクサクと処理できたので、Node.jsのファイルシステムを使ってローカルファイルを操作できるスクリプト集を作りました。
ローカルファイルを一括で修正したり、解析したり、スクリプトを組み合わせて色々なファイル操作に活用してみてください。

**今回つくったスクリプトはすべてGitHubに上げておきましたので、ダウンロードしてすぐお使いになれます。**

```bash
nodeFileSystem
├── changeLines.js
├── deleteFiles.js
├── grepFiles.js
├── listUpFiles.js
├── modules
│   └── fsCustom.js
├── readme.md
├── datas
└── results
```

GitHub：https://github.com/nkoutaka/nodeFileSystem

以下ではそれぞれの機能を逆引き辞典風にご紹介します。
# 特定のディレクトリ配下にあるファイルをリストアップしたい
特定のディレクトリ配下に存在するファイルを再帰的に取得し、ファイル名（ファイルパス）のリストを作成するスクリプトです。

```js
const fs = require('fs');

/**
 * 指定したディレクトリ配下のファイルを再帰的にリストアップする
 * @param {string} dirPath 対象ディレクトリのフルパス
 * @return {Array<string>} ファイルのフルパス
 */
const listFiles = dirPath => {
    const files = [];
    const paths = fs.readdirSync(dirPath);

    for (let name of paths) {
        try {
            const path = `${dirPath}/${name}`;
            const stat = fs.statSync(path);

            switch (true) {
                case stat.isFile():
                    files.push(path);
                    break;

                case stat.isDirectory():
                    files.push(...listFiles(path));
                    break;

                default:
            }
        } catch (err) {
            console.error('error:', e.message);
        }
    }

    return files;
};
```

`fs.readdirSync(dirPath)`ではディレクトリ直下のファイル一覧しか取得できないため、取得したデータを**ディレクトリ**と判定した場合はさらに取得処理を繰り返すことで、再帰的なファイル取得を実現しています。

**使用例**：現行のWEBサイトで使用している画像・CSS・JSファイルがどこにどのくらいあるか（総数・場所・ファイル名）を調べるため、実際のファイルベースでリストアップする際に使用。

# 特定のファイルが存在するか確かめたい
指定したパスのファイルが存在するか確認し、boolean値を返すスクリプトです。

```js
const fs = require('fs');
/**
 * 指定したフルパスのファイルが存在するか確認する
 * @param {string} filePath 対象とするファイルのフルパス
 * @return {boolean}
 */
const isExistFile = filePath => {
    try {
        fs.statSync(filePath);
        return true
    } catch (err) {
        if (err.code === 'ENOENT') return false
    }
};
```

`fs.statSync(filePath)`は対象バスが存在しない場合にエラーを返すため、try構文でラップしてあります。

**使用例**：用意したファイル一覧に従って各ファイルが存在するかしないかを調査する際に使用。

# 特定のファイルを削除したい
指定したパスのファイルを削除するスクリプトです。
削除するパスのリストファイル（例：`./data/deleteFiles.csv`）を用意して、一括削除もできます。

```js
const path = require('path');
const fs = require('fs');
const fsCustom = require('./modules/fsCustom');

//削除するファイルのパス
const targets = ['/hoge/hoge.html', '/hoge/hoge2.html', '/hoge/hoge3.html'];

//削除するパスのリストファイル
//const datas = fs.readFileSync('./data/deleteFiles.csv', 'utf8');
//const targets = datas.split(/\n/);

for (const target of targets) {
    const filePath = path.resolve(__dirname, target);
    try {
        if (fsCustom.isExistFile(filePath)) {
            fs.unlinkSync(filePath);
            console.log(`${filePath}を削除しました。`);
        }
    } catch (err) {
        console.error('error:', err.message);
    }
};
```

**使用例**：用意したファイル一覧に従って各ファイルを一括削除する際に使用。

# データサイズの大きなファイルを一行ずつ取得して修正したい
特定のディレクトリ配下にあるファイルを再帰的に取得し、一行ずつ修正していくスクリプトです。
例えば、一行ずつ正規表現で文字列を修正していき、それを一つのファイルにまとめるなど。修正処理部分は適宜変更してお使いください。

```js
const path = require('path');
const fs = require('fs');
const fsCustom = require('./modules/fsCustom');
const readline = require('readline');

//修正するファイルがあるディレクトリ
const dirPath = path.resolve(__dirname, './datas');
const files = fsCustom.listFiles(dirPath);

for (const file of files) {
    const stream = fs.createReadStream(file, 'utf8');
    const reader = readline.createInterface({ input: stream });
    reader.on('line', (data) => {
        //修正処理
        const match = data.match(/ ("[A-Z]{3,7} \/.+?) .+?" [0-9]{3} /);
        if (match && match[1]) {
            fs.appendFileSync(`./results/changeLines.csv`, `${match[1]}"\n`, (err, data) => {
                if (err) console.log(err);
                else console.log('write end');
            });
        }
    });
}
```

**使用例**：普通に開いたら落ちてしまうほど大きいサイズのファイルから必要な文字列だけを一行ずつ抽出して、新たに小さなサイズのファイルとしてまとめる際に使用。

# キーワードが含まれるファイルをリストアップしたい
特定のディレクトリ配下にあるファイルを再帰的に取得し、各キーワードが含まれるファイルをリストアップするスクリプトです。

```js
const path = require('path');
const fs = require('fs');
const fsCustom = require('./modules/fsCustom');

//検索ワードの配列
const keywords = ['/hoge/hoge.html', '/hoge/hoge2.html', '/hoge/hoge3.html']

//検索ワードのリストファイル
//const datas = fs.readFileSync('./data/grepFiles.csv', 'utf8');
//const keywords = datas.split(/\n/);

//検索対象のディレクトリ
const dirPath = path.resolve(__dirname, './');
const files = fsCustom.listFiles(dirPath);


for (let keyword of keywords) {

    const results = [keyword];

    keyword = keyword.replace(/\./g, '\\.');

    for (const file of files) {
        //正規表現で検索ワードを生成
        let regex = `("|'|\\|)(@\\{)?${keyword}.*`;
        let data = fs.readFileSync(file, 'utf8');
        let count = (data.match(new RegExp(regex, 'g')) || []).length;
        if (count) {
            results.push(file);
        }
    };
    fs.appendFileSync(`./results/grepFiles.csv`, results.join(',') + '\n', (err, data) => {
        if (err) console.log(err);
        else console.log('write end');
    });

};
```

**使用例**：現行のWEBサイトで「不要な画像ファイル」を探し出すために使用。CSS・JS・DOMファイルの中身を再帰的に検索し、画像ファイル名が全く含まれていない場合は不要と判定。

# まとめ
大きなデータサイズのファイルを処理する場合はメモリリークに気を使います（JavaScriptの変数に格納できるデータ量は１GBほど）。
その際はぜひ、Node.jsの非同期処理を活用してみましょう。

GitHub：https://github.com/nkoutaka/nodeFileSystem