はいさい!ちゅらデータぬオースティンやいびーん!
概要
CLIで実行するNode.jsのアプリケーションを実行する時に、CLIからアプリケーションに引数を渡して、そしてその渡された引数をアプリケーションで解析する方法を紹介します。
必要な知識
この記事を理解するためには、Node.jsを使ってCLIでJavaScriptを実行する知識が必要です。
参考になった記事
process.argv
を使い、Node.jsアプリケーションでCLI引数を読む
Node.jsのアプリケーションには、process
のグローバル変数があり、この変数には、環境変数を始めとして、さまざまな便利な情報にアクセスすることができます。
今回、CLIの引数にアクセスするためには、process.argv
を使います。
以下のようなJavaScriptを書いて、
#!/usr/bin/env node
console.log(process.argv);
そしてNode.jsで実行してみますと、
node index.js --user churataro
以下の配列が出力されます。
[
'/usr/local/bin/node',
'/Users/austin.mayer/Documents/practice/apply-saml-creds/test.js',
'--user',
'churataro'
]
文字列の配列なのです。そして、指定もしていない謎の文字列がIndex 0とIndex 1にきているのではないでしょうか。
この二つの文字列は何なのでしょうか?
Index 0の'/usr/local/bin/node'
これは、このindex.js
を実行するのに使ったNode.jsのバイナリーのローカルパスです。つまり、ローカルにインストールしてあるNode.jsがどこにインストールされているかを教えてくれています。
Index 1の'/Users/austin.mayer/Documents/practice/apply-saml-creds/index.js'
これは、index.js
のローカルパスです。つまり、index.js
を実行している時のNode.jsのPATHになります。
Index 2以降
Index 2以降の値は、CLIで渡された引数になります。空白で分けてあります。
この部分の引数を解析したいので、通常、以下のようにIndex 2までの引数を無視して使います。
#!/usr/bin/env node
const args = process.argv.slice(2);
console.log(args);
キーと値でCLI引数を解析するロジックを作成する
次、CLI引数をキーと値にまとめてアプリケーションで使えるようにするためのロジックを書いていきましょう。
ここからはTypeScriptで書きますので、tsc
を実行して、コンパイルされたものを実行しましょう。
今回のアプリケーションでは--your-flag FLAG1
という形式で引数をCLIで指定する前提でロジックを作成します。
以下の関数をご覧ください。
const convertArgumentsToMap = (providedArguments: string[]) => {
const argumentsMap = new Map<string, string | boolean>();
const keyRegex = /--[a-z]{1,}-?[a-z]{1,}/;
for (let index = 0; index < providedArguments.length; index += 2) {
const key = providedArguments[index];
if (!key) break;
if (!keyRegex.test(key)) throw Error(`Invalid key provided: "${key}". Keys must be provided with two hyphens.`);
const value = providedArguments[index + 1];
argumentsMap.set(key, value ?? true);
}
return argumentsMap;
};
まず、const argumentsMap = new Map<string, string | boolean>();
で引数のキーと値を保管するMap
を作ります。
それから、--flag-name FLAG-VALUE
という形式を取っているので、キーの--flag-name
のように正しい形式で指定してもらっているかどうかを打診するために、正規表現の/--[a-z]{1,}-?[a-z]{1,}/
を使います。
ちなみに、--flag
のように、2番目のハイフンは任意なので、正規表現で?
を使っています。本来だったら、回帰的な処理ができるような書き方をしたいところですが、JavaScriptではその正規表現ができないらしいです。
次に、for
ループという、パッとしない手段で、一回の処理で配列の二つの値を引っ張り出しています。
一つ目の値はキーになるはずなので、それに対しては正規表現のテストを実行して正しい形式になっているかどうかを確かめます。
その上、配列の次の値も抽出して、その値があるかどうかをチェックした上で、argumentsMap
に入れておきます。値がなかったら、boolean
系のFlagだということでtrue
を入れます。
CLI引数配列の最後までくると、argumentMap
を返して終わり!
試しに引数を渡してみる
ここで、上記書いた関数を使って、CLI引数を解析してみます。
#!/usr/bin/env node
const convertArgumentsToMap = (providedArguments) => {
const argumentsMap = new Map();
const keyRegex = /--[a-z]{1,}-?[a-z]{1,}/;
for (let index = 0; index < providedArguments.length; index += 2) {
const key = providedArguments[index];
if (!key) break;
if (!keyRegex.test(key)) throw Error(`Invalid key provided: "${key}". Keys must be provided with two hyphens.`);
const value = providedArguments[index + 1];
argumentsMap.set(key, value ?? true);
}
return argumentsMap;
};
const args = process.argv.slice(2);
const argsMap = convertArgumentsToMap(args);
console.log(argsMap);
上記のスクリプトを以下のCLI引数で実行してみます。
node test.js --user churataro --password churadata
すると、Map
が以下のように出力されます。
Map(2) { '--user' => 'churataro', '--password' => 'churadata' }
試しに、正しくない形式で引数を渡して実行してみます。
node test.js user churataro --password churadata
すると以下のようにエラーが表示されます。
Error: Invalid key provided: "user". Keys must be provided with two hyphens.
Node.jsのnode:utils.parseArgs
を使う
注意 こちらは、試験的な機能で、最新のNode.js 18.8でしか使えない!
最新のNode.jsには、もっと合理的な引数を解析してくれるツールが入っています。それはnode:utils.parseArgs
です。
以下、parseArgs
の使い方を説明してまいります。
parseArgs
の引数について
parseArgs
を実行する時に、引数にオブジェクトを渡します。
その中の必須な設定を説明します。
options
こちらは、CLI引数でどのような引数を受け入れて使うかを設定するものです。
こちらもオブジェクトになりますが、以下のような型になっています。
type Option = {
type: "boolean" | "string"; // 必須
short?: string; // 任意、省略フラグを定義するのに使う
multiple?: boolean; // 任意、複数回指定していいかどうか
};
このtype Option
からなるオブジェクトなので、以下のような書き方ができます。
const options: Record<string, Option> = {
"account-number": {
type: "string",
short: "a",
multiple: false,
},
role: {
type: "string",
short: "r",
multiple: false,
},
};
こうして指定したoptions
は後ほどparseArgs
で使います。
args
こちらは、process.argv
から取るCLI引数です。
const args = process.argv.slice(2);
使ってみる
上記の情報を使って実行してみます。
#!/usr/bin/env node
const { parseArgs } = require("node:util");
const options = {
"account-number": {
type: "string",
short: "a",
multiple: false,
},
role: {
type: "string",
short: "r",
multiple: false,
},
};
const args = process.argv.slice(2);
const parsedArgs = parseArgs({ options, args });
console.log(parsedArgs);
以下のようなコマンドで実行してみましょう。
node test.js --account-number 100 --role Admin
結果:
{
values: [Object: null prototype] { 'account-number': '100', role: 'Admin' },
positionals: []
}
ちゃんと、account-number
とrole
が取れていますね!
*positionals
については今回の記事で説明しません。気になる方は公式ドキュメントをご参照ください。
上記のoptions
で任意のshort
も設定しているのですが、short
を設定するとCLIでは以下のような引数の渡し方ができます。
node test.js -a 100 -r Admin
また、設定されていない引数を渡すと、以下のようなエラーが出ます。
node test.js --account-number 100 --role Admin --unknown
結果:
TypeError [ERR_PARSE_ARGS_UNKNOWN_OPTION]: Unknown option '--unknown'
at new NodeError (node:internal/errors:393:5)
at checkOptionUsage (node:internal/util/parse_args/parse_args:97:11)
at node:internal/util/parse_args/parse_args:327:9
at Array.forEach (<anonymous>)
at parseArgs (node:internal/util/parse_args/parse_args:324:3)
at Object.<anonymous> (/Users/austin.mayer/Documents/practice/apply-saml-creds/test.js:19:20)
at Module._compile (node:internal/modules/cjs/loader:1119:14)
at Module._extensions..js (node:internal/modules/cjs/loader:1173:10)
at Module.load (node:internal/modules/cjs/loader:997:32)
at Module._load (node:internal/modules/cjs/loader:838:12) {
code: 'ERR_PARSE_ARGS_UNKNOWN_OPTION'
}
自作のコードより便利なので、使いたい!と思いますが、まだ試験的な段階なので、完全に推奨できません。今後の楽しみにしておきましょう!
まとめ
以上、Node.jsでCLI引数を解析する方法を紹介してまいりましたが、いかがでしょうか?
最後に紹介したparseArgs
が待ち遠しいですね。
今回、CLI引数を解析する方法について調べるきっかけは、筆者が初めてnpmに公開したパッケージで使うからです。
そのパッケージは、AWSにSAMLでログインした場合、そのSAML Responseを使って、ローカルの.aws/credentials
ファイルにも反映させるスクリプトです。
筆者は、毎回手書きでやるのがしににりーだったので、Nodeでやったら、面白いはず!
と思い、またさらに、npmに公開する方法もついでに勉強したらいいはず!
とも思い、随分楽しい経験になりました。
実際、誰1人として使うとは到底思いませんが!(笑)