コマンドライン引数を処理する
このユースケースで作成するCLIアプリケーションの目的は、コマンドライン引数として与えられたファイルを変換することです。 このセクションではコマンドライン引数を受け取って、それをパースするところまでを行います。
process
オブジェクトとコマンドライン引数
コマンドライン引数を扱う前に、まずはprocess
オブジェクトについて触れておきます。
process
オブジェクトはNode.js実行環境のグローバル変数のひとつです。
process
オブジェクトが提供するのは、現在のNode.jsの実行プロセスについて、情報の取得と操作をするAPIです。
詳細は公式ドキュメントを参照してください。
コマンドライン引数へのアクセスを提供するのは、process
オブジェクトのargv
プロパティで、文字列の配列になっています。
次のようにmain.js
を変更し、process.argv
をコンソールに出力しましょう。
main.js
// コンソールにコマンドライン引数を出力する
console.log(process.argv);
このスクリプトを次のようにコマンドライン引数をつけて実行してみましょう。
$ node main.js one two=three four
このコマンドの実行結果は次のようになります。
[
'/usr/local/bin/node', // Node.jsの実行プロセスのパス
'/Users/laco/nodecli/main.js', // 実行したスクリプトファイルのパス
'one', // 1番目の引数
'two=three', // 2番目
'four' // 3番目
]
1番目と2番目の要素は常にnode
コマンドと実行されたスクリプトのファイルパスになります。
つまりアプリケーションがコマンドライン引数として使うのは、3番目以降の要素です。
コマンドライン引数をパースする
process.argv
配列を使えばコマンドライン引数を取得できますが、取得できる情報にはアプリケーションに不要なものも含まれています。
また、文字列の配列として渡されるため、フラグのオンオフのような真偽値を受け取るときにも不便です。
そのため、アプリケーションでコマンドライン引数を扱うときには、一度パースして扱いやすい値に整形するのが一般的です。
今回はcommanderというライブラリを使ってコマンドライン引数をパースしてみましょう。 文字列処理を自前で行うこともできますが、このような一般的な処理は既存のライブラリを使うと簡単に書けます。
commander
パッケージをインストールする
commanderはnpmのnpm install
コマンドを使ってインストールできます。
まだnpmの実行環境を用意できていなければ、先に「アプリケーション開発の準備」の章を参照してください。
npmでパッケージをインストールする前に、まずはpacakge.json
というファイルを作成します。
package.json
とは、アプリケーションが依存するパッケージの種類やバージョンなどの情報を記録するJSON形式のファイルです。
package.json
ファイルのひな形は、npm init
コマンドで生成できます。
通常は対話式のプロンプトによって情報を設定しますが、ここではすべてデフォルト値でpacakge.json
を作成する--yes
オプションを付与します。
nodecli
のディレクトリ内で、npm init --yes
コマンドを実行してpacakge.json
を作成しましょう。
$ npm init --yes
生成されたpackage.json
ファイルは次のようになっています。
package.json
{
"name": "nodecli",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
package.json
ファイルが用意できたら、npm install
コマンドを使ってcommander
パッケージをインストールします。
このコマンドの引数にはインストールするパッケージの名前とそのバージョンを@
記号でつなげて指定できます。
バージョンを指定せずにインストールすれば、その時点での最新の安定版が自動的に選択されます。
次のコマンドを実行して、commanderのバージョン5.0をインストールします。1
$ npm install commander@5.0
インストールが完了すると、package.json
ファイルは次のようになっています。
package.json
{
"name": "nodecli",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"commander": "^5.0.0"
}
}
また、npmのバージョンが5以上であれば package-lock.json
ファイルが生成されています。
このファイルはnpmがインストールしたパッケージの、実際のバージョンを記録するためのものです。
先ほどcommanderのバージョンを5.0
としましたが、実際にインストールされるのは5.0.x
に一致する最新のバージョンです。
package-lock.json
ファイルには実際にインストールされたバージョンが記録されています。
これによって、再びnpm install
を実行したときに、異なるバージョンがインストールされるのを防ぎます。
CommonJSモジュール
インストールしたcommander
パッケージを使う前に、CommonJSモジュールのことを知っておきましょう。
CommonJSモジュールとは、Node.js環境で利用されているJavaScriptのモジュール化の仕組みです。
CommonJSモジュールは基本文法で学んだECMAScriptモジュールの仕様が策定されるより前からNode.jsで使われています。
Node.jsの標準パッケージやnpmで配布されるパッケージは、CommonJSモジュールとして提供されていることがほとんどです。
先ほどインストールしたcommander
パッケージも、CommonJSモジュールとして利用できます。
CommonJSモジュールはNode.jsのグローバル変数であるmodule
変数を使って変数や関数などをエクスポートします。
CommonJSモジュールではmodule.exports
プロパティに代入されたオブジェクトが、そのJavaScriptファイルからエクスポートされます。
複数の名前つきエクスポートが可能なES Moduleとは異なり、CommonJSではmodule.exports
プロパティの値だけがエクスポートの対象です。
次の例では、my-module.js
というファイルを作成し、module.exports
でオブジェクトをエクスポートしています。
my-module.js
module.exports = {
foo: "foo"
};
このCommonJSモジュールをインポートするには、Node.js実行環境のグローバル関数であるrequire関数を使います。
次のようにrequire
関数にインポートしたいモジュールのファイルパスを渡し、戻り値としてエクスポートされた値をインポートできます。
インポートするファイルパスに拡張子が必須なES Moduleとは異なり、CommonJSのrequire
関数では拡張子である.js
が省略可能です。
// my-module.jsモジュールをmyModuleオブジェクトとしてインポートする。
const myModule = require("./my-module");
console.log(myModule.foo); // => "foo"
また、require
関数は相対パスや絶対パス以外にもnpmでインストールしたパッケージ名を指定できます。
npm install
コマンドでインストールされたパッケージは、node_modules
というディレクトリの中に配置されています。
require
関数にインストールしたパッケージ名を指定することで、node_modules
ディレクトリに配置されたパッケージを読み込めます。
次の例では、先ほどインストールしたcommander
パッケージをnode_modules
ディレクトリから読み込んでいます。
const program = require("commander");
このユースケースで今後登場するモジュールはすべてCommonJSモジュールです。 Node.jsではES Moduleもサポートされる予定ですが、現在はまだ安定した機能としてサポートされていません。
コマンドライン引数からファイルパスを取得する
先ほどインストールしたcommander
パッケージを使って、コマンドライン引数として渡されたファイルパスを取得しましょう。
このCLIアプリケーションでは、処理の対象とするファイルパスを次のようなコマンドの形式で受け取ります。
$ node main.js ./sample.md
commanderでコマンドライン引数をパースするためには、parse
メソッドにコマンドライン引数を渡します。
// commanderモジュールをprogramオブジェクトとしてインポートする
const program = require("commander");
// コマンドライン引数をパースする
program.parse(process.argv);
parse
メソッドを呼び出すと、コマンドライン引数をパースした結果をprogram
オブジェクトから取り出せるようになります。
今回の例では、ファイルパスはprogram.args
配列に格納されています。
program.args
配列には--key=value
のようなオプションや--flag
のようなフラグを取り除いた残りのコマンドライン引数が順番に格納されています。
それではmain.js
を次のように変更し、コマンドライン引数で渡されたファイルパスを取得しましょう。
main.js
// commanderモジュールをprogramとしてインポートする
const program = require("commander");
// コマンドライン引数をcommanderでパースする
program.parse(process.argv);
// ファイルパスをprogram.args配列から取り出す
const filePath = program.args[0];
console.log(filePath);
次のコマンドを実行すると、program.args
配列に格納された./sample.md
文字列が取得されてコンソールに出力されます。
./sample.md
はprocess.argv
配列では3番目に存在していましたが、パース後のprogram.args
配列では1番目になって扱いやすくなっています。
$ node main.js ./sample.md
./sample.md
このように、process.argv
配列を直接扱うよりも、commanderのようなライブラリを使うことで宣言的にコマンドライン引数を定義して処理できます。
次のセクションではコマンドライン引数から取得したファイルパスを元に、ファイルを読み込む処理を追加していきます。
このセクションのチェックリスト
process.argv
配列にnode
コマンドのコマンドライン引数が格納されていることを確認した- npmを使ってパッケージをインストールする方法を理解した
require
関数を使ってパッケージのモジュールを読み込めることを確認した- commanderを使ってコマンドライン引数をパースできることを確認した
- コマンドライン引数で渡されたファイルパスを取得してコンソールに出力できた
1. --saveオプションをつけてインストールしたのと同じ意味。npm 5.0.0からは--saveがデフォルトオプションとなりました。 ↩