初心者｜node.jsでリネーム。フォルダからファイル名一覧を取得し一括変更する

あるディレクトリの中にあるファイルたちの名前をまとめて変更したいなぁ、というときがあり、node.jsでやってみました。

# 要件
* node.js がインストールされている
* 特定のフォルダ内のファイルを一括リネームしたい
* 対象のファイルを種類（拡張子）で抽出したい（フィルタリングしたい）

# 例
<img width="452" alt="スクリーンショット 2020-03-12 14.50.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/a801dca5-d9fb-5cb2-6b17-4718db5f4a0d.png">

* ディレクトリ「textBox」内にある「●●●.txt」ファイルをリネームします（他の拡張子は無視）
* リネームの内容：ファイル名の先頭にディレクトリ名を追加します（「ディレクトリ名_ファイル名」にする）
* 実行するjsファイル名は「changeName.js」にしています（任意のものでOK）

# 手順
## 1）jsファイルの作成
```js:changeName.js
const fs = require("fs");
const path = require("path");

const dir = "./textBox"; // ← 変更してね
const addHead = "textBox_"; // ← 変更してね
const fileNameList = fs.readdirSync(dir);
const targetFileNames = fileNameList.filter(RegExp.prototype.test, /.*\.txt$/); // ← 変更してね
// console.log(targetFileNames);

targetFileNames.forEach(fileName => {
  // console.log(fileName)
  const filePath = {};
  const newName = addHead + fileName;
  filePath.before = path.join(dir, fileName);
  filePath.after = path.join(dir, newName);
  // console.log(filePath);
  fs.rename(filePath.before, filePath.after, err => {
    if (err) throw err;
    console.log(filePath.before + "-->" + filePath.after);
  });
});
```
* `const dir = "./textBox"` には `changeName.js` のあるカレントディレクトリからのパスを入力
* `const addHead = "textBox_";` にはファイル名の先頭に付与する文字列を入力
*  `const targetFileNames = fileNameList.filter(RegExp.prototype.test, /.*\.txt$/);`の後半部の `.txt` で抽出したいファイルの拡張子を指定

## 2）jsファイルの実行
```terminal:terminal
$ node changeName.js
```
<img width="118" alt="スクリーンショット 2020-03-12 15.11.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283603/9ae6a6ad-2b17-3155-376a-205968b25a56.png">

以上です。

---

# 解説
```js:changeName.js
const fs = require("fs"); // nodeのfsモジュール読み込み
const path = require("path"); // nodeのpathモジュール読み込み

const dir = "./textBox"; // 対象のディレクトリを指定
const addHead = "textBox_"; // ファイル名に付与したい文字列を入力

const fileNameList = fs.readdirSync(dir); // 対象ディレクトリからファイル名一覧を取得
console.log(fileNameList); // [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt', 'xxx.jpg' ]

const targetFileNames = fileNameList.filter(RegExp.prototype.test, /.*\.txt$/); // ファイル名一覧から、拡張子で抽出
console.log(targetFileNames); // [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt' ]

/* 拡張子で抽出したファイル名一覧「targetFileNames」を forEach で回す */
targetFileNames.forEach(fileName => {
  console.log(fileName); // textBox_aaa.txt
  const filePath = {}; // ファイル名の前後を入れる箱（連想配列）
  const newName = addHead + fileName; // 元のファイル名の先頭に、文字列「addHead」を加える
  filePath.before = path.join(dir, fileName); // 変更前のフルパス（ディレクトリパス＋ファイル名）を箱に入れる
  filePath.after = path.join(dir, newName); // 変更後のフルパスを箱に入れる
  console.log(filePath); // { before: 'textBox/aaa.txt', after: 'textBox/textBox_aaa.txt' }

  /* リネーム処理 */
  fs.rename(filePath.before, filePath.after, err => {
    if (err) throw err;
    console.log(filePath.before + "-->" + filePath.after); // textBox/aaa.txt-->textBox/textBox_aaa.txt
  });
  /* END リネーム処理 */

});
/* END forEach */
```

## require
本件は、node.jsの組み込みモジュール（標準モジュール）だけで実施可能です。
公式ドキュメント　→　[File System](https://nodejs.org/docs/latest-v13.x/api/fs.html)　[Path](https://nodejs.org/docs/latest-v13.x/api/path.html)

```js:changeName.js（抜粋）
const fs = require("fs"); // nodeのfsモジュール読み込み
const path = require("path"); // nodeのpathモジュール読み込み
```

## fs.readdirSync(path)
指定フォルダ（path）内のファイル・サブフォルダ名を取得します（[詳細](https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_fs_readdirsync_path_options)）。
フィルタリングする前なので「xxx.jpg」も含まれています。

```js:changeName.js（抜粋）
const fileNameList = fs.readdirSync(dir); // 対象ディレクトリからファイル名一覧を取得
console.log(fileNameList); // [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt', 'xxx.jpg' ]
```

## Array.filter()
> filter() メソッドは、引数として与えられたテスト関数を各配列要素に対して実行し、それに合格したすべての配列要素からなる新しい配列を生成します。（[詳細](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)）

ようは、「●●●.txt」だけを抽出するフィルタとして使っています。
もしフィルタが不要でしたら `.filter(RegExp.prototype.test, /.*\.txt$/)` を除去すればOKです。
（もしくは `forEach()` にはフィルタ前の `fileNameList` を渡すようにする）

```js:changeName.js（抜粋）
const targetFileNames = fileNameList.filter(RegExp.prototype.test, /.*\.txt$/); // ファイル名一覧から、拡張子で抽出
console.log(targetFileNames); // [ 'textBox_aaa.txt', 'textBox_bbb.txt', 'textBox_ccc.txt' ]
```

## Array.forEach()
> forEach() メソッドは与えられた関数を、配列の各要素に対して一度ずつ実行します。（[詳細](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)）

拡張子で抽出したファイル名一覧「targetFileNames」からファイル名を１つずつ取り出して、リネームの処理を実行していきます。

```js:changeName.js（抜粋）
targetFileNames.forEach(fileName => {
  console.log(fileName); // textBox_aaa.txt
  // 〜省略〜
});
```

## 連想配列にリネーム前後のファイル名を入れる
```js:changeName.js（抜粋）
  const filePath = {};
  const newName = addHead + fileName;
  filePath.before = path.join(dir, fileName);
  filePath.after = path.join(dir, newName);
  console.log(filePath); // { before: 'textBox/aaa.txt', after: 'textBox/textBox_textBox_aaa.txt' }
```

`filePath` という連想配列の箱を用意して、その中にリネーム前とリネーム後のファイル名を入れています。
リネームするときはフルパス（ディレクトリパス＋ファイル名）が必要なので、 `path.join` でフルパスにしています（[詳細](https://nodejs.org/api/path.html#path_path_join_paths)）。

## fs.rename(oldPath, newPath, callback)
リネーム処理をする部分です。第一引数に変更前のファイルのフルパス、第二引数には変更後のファイルのフルパスを入れます（[詳細](https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_fs_rename_oldpath_newpath_callback)）。

```js:changeName.js（抜粋）
  fs.rename(filePath.before, filePath.after, err => {
    if (err) throw err;
    console.log(filePath.before + "-->" + filePath.after); // textBox/aaa.txt-->textBox/textBox_aaa.txt
  });
```

---

# あとがき
【注意！】　本件を試す際は、どうかリネーム前にフォルダ（ファイル）のコピーを作成ください。リネーム後に戻すのは大変ですよ！

本件はファイル名の先頭に文字を足すだけでしたが、文字列を検出して置換したり、連番を振ったり...処理を足して色々と応用できると思います。

お役に立てば幸いです。


# リスペクトな記事
本件のために参照させていただきました記事です。

* [node.js　ファイル一覧を取得する](https://qiita.com/_shimizu/items/f08eaacdbcdce0204e36)
* [match()とreplace()を使ってファイル名を変更する！（ちょっと対話型）：Node.js](http://mmorley.hatenablog.com/entry/2017/01/06/231535)