コマンドライン引数を処理する

このユースケースで作成する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はnpmnpm 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のバージョン2.9をインストールします。1

$ npm install commander@2.9

インストールが完了すると、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": "^2.9.0"
  }
}

また、npmのバージョンが5以上であれば package-lock.jsonファイルが生成されています。 このファイルはnpmがインストールしたパッケージの、実際のバージョンを記録するためのものです。 さきほどcommanderのバージョンは2.9としましたが、実際にインストールされるのは2.9.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.mdprocess.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がデフォルトオプションとなりました。