util.parseArgs() を使ったコマンドライン引数の解析parseArgs()parseArgs トークンこの章では、モジュール node:util の Node.js 関数 parseArgs() を使用してコマンドライン引数を解析する方法を説明します。
この章のすべての例では、以下の2つのインポートが暗黙的に使用されています。
import * as assert from 'node:assert/strict';
import {parseArgs} from 'node:util';最初のインポートは、値をチェックするために使用するテストアサーション用です。2番目のインポートは、この章のトピックである関数 parseArgs() 用です。
コマンドライン引数の処理には、以下の手順が含まれます。
process.argv に格納された配列を介して単語を受け取ります。 process は Node.js のグローバル変数です。parseArgs() を使用して、その配列をより扱いやすいものに変換します。Node.js コードを含む次のシェルスクリプト args.mjs を使用して、process.argv がどのように見えるかを確認しましょう。
#!/usr/bin/env node
console.log(process.argv);簡単なコマンドから始めます。
% ./args.mjs one two
[ '/usr/bin/node', '/home/john/args.mjs', 'one', 'two' ]
Windows で npm を介してコマンドをインストールする場合、Windows コマンドシェルでは同じコマンドで次の結果が得られます。
[
'C:\\Program Files\\nodejs\\node.exe',
'C:\\Users\\jane\\args.mjs',
'one',
'two'
]シェルスクリプトをどのように呼び出すかにかかわらず、process.argv は常にコードを実行するために使用される Node.js バイナリのパスから始まります。 次に、スクリプトのパスが続きます。配列は、スクリプトに渡された実際の引数で終わります。言い換えれば、スクリプトの引数は常にインデックス2から始まります。
したがって、スクリプトを次のように変更します。
#!/usr/bin/env node
console.log(process.argv.slice(2));より複雑な引数を試してみましょう。
% ./args.mjs --str abc --bool home.html main.js
[ '--str', 'abc', '--bool', 'home.html', 'main.js' ]
これらの引数は以下で構成されています。
abc のオプション --str。このようなオプションは、*文字列オプション* と呼ばれます。--bool – そこにあるかどうかのフラグです。このようなオプションは、*ブールオプション* と呼ばれます。home.html と main.js。引数の使用には 2 つの一般的なスタイルがあります。
前の例を JavaScript 関数呼び出しとして記述すると、次のようになります(JavaScript では、通常、オプションは最後に配置されます)。
argsMjs('home.html', 'main.js', {str: 'abc', bool: false});parseArgs() に引数を含む配列を解析させるには、まずオプションの動作を伝える必要があります。スクリプトに以下があると仮定します。
--verbose--times。parseArgs() は数値の特別なサポートがないため、文字列オプションにする必要があります。--colorこれらのオプションを parseArgs() に次のように記述します。
const options = {
'verbose': {
type: 'boolean',
short: 'v',
},
'color': {
type: 'string',
short: 'c',
},
'times': {
type: 'string',
short: 't',
},
};options のプロパティキーが有効な JavaScript 識別子である限り、それを引用するかどうかはあなた次第です。どちらにも長所と短所があります。この章では、常に引用されます。そうすることで、my-new-option などの非識別子名を持つオプションは、識別子名を持つオプションと同じように表示されます。
options の各エントリには、次のプロパティ(TypeScript 型で定義)を含めることができます。
type Options = {
type: 'boolean' | 'string', // required
short?: string, // optional
multiple?: boolean, // optional, default `false`
};.type は、オプションがブール値か文字列かを指定します。.short は、オプションのショートバージョンを定義します。これは、単一の文字である必要があります。ショートバージョンの使用方法はすぐに説明します。.multiple は、オプションを最大で1回使用できるか、0回以上使用できるかを示します。その意味については後で説明します。次のコードでは、parseArgs() と options を使用して引数を含む配列を解析します。
assert.deepEqual(
parseArgs({options, args: [
'--verbose', '--color', 'green', '--times', '5'
]}),
{
values: {__proto__:null,
verbose: true,
color: 'green',
times: '5'
},
positionals: []
}
);.values に格納されたオブジェクトのプロトタイプは null です。つまり、.toString などの継承されたプロパティを気にすることなく、in 演算子を使用してプロパティが存在するかどうかを確認できます。
前述したように、--times の値である数値 5 は、文字列として処理されます。
parseArgs() に渡すオブジェクトには、次の TypeScript 型があります。
type ParseArgsProps = {
options?: {[key: string], Options}, // optional, default: {}
args?: Array<string>, // optional
// default: process.argv.slice(2)
strict?: boolean, // optional, default `true`
allowPositionals?: boolean, // optional, default `false`
};.args: 解析する引数。このプロパティを省略した場合、parseArgs() はインデックス 2 の要素から始まる process.argv を使用します。.strict: true の場合、args が正しくないと例外がスローされます。詳細については後述します。.allowPositionals: args に位置引数を含めることができますか。これは parseArgs() の結果の型です。
type ParseArgsResult = {
values: {[key: string]: ValuesValue}, // an object
positionals: Array<string>, // always an Array
};
type ValuesValue = boolean | string | Array<boolean|string>;.values には、オプションの引数が含まれています。プロパティ値として文字列とブール値はすでに説明しました。.multiple が true であるオプション定義を検討するときに、配列値のプロパティについて説明します。.positionals には、位置引数が含まれています。2 つのハイフンはオプションのロングバージョンを参照するために使用されます。1 つのハイフンはショートバージョンを参照するために使用されます。
assert.deepEqual(
parseArgs({options, args: ['-v', '-c', 'green']}),
{
values: {__proto__:null,
verbose: true,
color: 'green',
},
positionals: []
}
);.values にはオプションのロング名が含まれていることに注意してください。
このサブセクションでは、オプション引数と混在する位置引数を解析して締めくくります。
assert.deepEqual(
parseArgs({
options,
allowPositionals: true,
args: [
'home.html', '--verbose', 'main.js', '--color', 'red', 'post.md'
]
}),
{
values: {__proto__:null,
verbose: true,
color: 'red',
},
positionals: [
'home.html', 'main.js', 'post.md'
]
}
);オプションを複数回使用する場合、デフォルトでは、最後の回のみがカウントされます。以前のすべての出現を上書きします。
const options = {
'bool': {
type: 'boolean',
},
'str': {
type: 'string',
},
};
assert.deepEqual(
parseArgs({
options, args: [
'--bool', '--bool', '--str', 'yes', '--str', 'no'
]
}),
{
values: {__proto__:null,
bool: true,
str: 'no'
},
positionals: []
}
);ただし、オプションの定義で .multiple を true に設定すると、parseArgs() はすべてのオプション値を配列で返します。
const options = {
'bool': {
type: 'boolean',
multiple: true,
},
'str': {
type: 'string',
multiple: true,
},
};
assert.deepEqual(
parseArgs({
options, args: [
'--bool', '--bool', '--str', 'yes', '--str', 'no'
]
}),
{
values: {__proto__:null,
bool: [ true, true ],
str: [ 'yes', 'no' ]
},
positionals: []
}
);次のオプションを検討してください。
const options = {
'verbose': {
type: 'boolean',
short: 'v',
},
'silent': {
type: 'boolean',
short: 's',
},
'color': {
type: 'string',
short: 'c',
},
};以下は、複数のブールオプションを使用するコンパクトな方法です。
assert.deepEqual(
parseArgs({options, args: ['-vs']}),
{
values: {__proto__:null,
verbose: true,
silent: true,
},
positionals: []
}
);等号を使用して、ロング文字列オプションの値を直接アタッチできます。これは、*インライン値* と呼ばれます。
assert.deepEqual(
parseArgs({options, args: ['--color=green']}),
{
values: {__proto__:null,
color: 'green'
},
positionals: []
}
);ショートオプションにはインライン値を設定できません。
これまでのところ、すべてのオプション値と位置値は単語でした。スペースを含む値を使用する場合は、二重引用符または一重引用符で囲む必要があります。ただし、後者はすべてのシェルでサポートされているわけではありません。
シェルが引用符付きの値を解析する方法を調べるために、スクリプト args.mjs を再度使用します。
#!/usr/bin/env node
console.log(process.argv.slice(2));Unix では、二重引用符と一重引用符の間には次の違いがあります。
二重引用符: バックスラッシュを使用して引用符をエスケープできます(それ以外の場合はそのまま渡されます)。また、変数が補間されます。
% ./args.mjs "say \"hi\"" "\t\n" "$USER"
[ 'say "hi"', '\\t\\n', 'rauschma' ]一重引用符: すべてのコンテンツがそのまま渡され、引用符をエスケープすることはできません。
% ./args.mjs 'back slash\' '\t\n' '$USER'
[ 'back slash\\', '\\t\\n', '$USER' ]次のやり取りは、二重引用符と一重引用符で囲まれたオプションの値を示しています。
% ./args.mjs --str "two words" --str 'two words'
[ '--str', 'two words', '--str', 'two words' ]
% ./args.mjs --str="two words" --str='two words'
[ '--str=two words', '--str=two words' ]
% ./args.mjs -s "two words" -s 'two words'
[ '-s', 'two words', '-s', 'two words' ]
Windows コマンドシェルでは、一重引用符は特殊な意味を持ちません。
>node args.mjs "say \"hi\"" "\t\n" "%USERNAME%"
[ 'say "hi"', '\\t\\n', 'jane' ]
>node args.mjs 'back slash\' '\t\n' '%USERNAME%'
[ "'back", "slash\\'", "'\\t\\n'", "'jane'" ]
Windows コマンドシェルでの引用符付きオプション値
>node args.mjs --str 'two words' --str "two words"
[ '--str', "'two", "words'", '--str', 'two words' ]
>node args.mjs --str='two words' --str="two words"
[ "--str='two", "words'", '--str=two words' ]
>>node args.mjs -s "two words" -s 'two words'
[ '-s', 'two words', '-s', "'two", "words'" ]
Windows PowerShell では、一重引用符で囲むことができます。引用符内の変数名は補間されず、一重引用符はエスケープできません。
> node args.mjs "say `"hi`"" "\t\n" "%USERNAME%"
[ 'say hi', '\\t\\n', '%USERNAME%' ]
> node args.mjs 'backtick`' '\t\n' '%USERNAME%'
[ 'backtick`', '\\t\\n', '%USERNAME%' ]
parseArgs() が引用符付きの値を処理する方法parseArgs() が引用符付きの値を処理する方法を以下に示します。
const options = {
'times': {
type: 'string',
short: 't',
},
'color': {
type: 'string',
short: 'c',
},
};
// Quoted external option values
assert.deepEqual(
parseArgs({
options,
args: ['-t', '5 times', '--color', 'light green']
}),
{
values: {__proto__:null,
times: '5 times',
color: 'light green',
},
positionals: []
}
);
// Quoted inline option values
assert.deepEqual(
parseArgs({
options,
args: ['--color=light green']
}),
{
values: {__proto__:null,
color: 'light green',
},
positionals: []
}
);
// Quoted positional values
assert.deepEqual(
parseArgs({
options, allowPositionals: true,
args: ['two words', 'more words']
}),
{
values: {__proto__:null,
},
positionals: [ 'two words', 'more words' ]
}
);parseArgs() は、いわゆる*オプションターミネータ*をサポートしています。args の要素の 1 つが二重ハイフン (--) である場合、残りの引数はすべて位置引数として扱われます。
オプションターミネータはどこで必要になりますか。一部の実行可能ファイルは他の実行可能ファイルを呼び出します。例: node 実行可能ファイル。次に、オプションターミネータを使用して、呼び出し元の引数と呼び出し先の引数を分離できます。
parseArgs() がオプションターミネータを処理する方法を以下に示します。
const options = {
'verbose': {
type: 'boolean',
},
'count': {
type: 'string',
},
};
assert.deepEqual(
parseArgs({options, allowPositionals: true,
args: [
'how', '--verbose', 'are', '--', '--count', '5', 'you'
]
}),
{
values: {__proto__:null,
verbose: true
},
positionals: [ 'how', 'are', '--count', '5', 'you' ]
}
);parseArgs()オプション .strict が true(デフォルト)の場合、次のいずれかが発生すると、parseArgs() は例外をスローします。
args で使用されるオプションの名前が options にありません。args のオプションの型が間違っています。現在、文字列オプションに引数が不足している場合にのみ発生します。.allowPositions が false (デフォルト)であるにもかかわらず、args に位置引数があります。次のコードは、これらの各ケースを示しています。
const options = {
'str': {
type: 'string',
},
};
// Unknown option name
assert.throws(
() => parseArgs({
options,
args: ['--unknown']
}),
{
name: 'TypeError',
message: "Unknown option '--unknown'",
}
);
// Wrong option type (missing value)
assert.throws(
() => parseArgs({
options,
args: ['--str']
}),
{
name: 'TypeError',
message: "Option '--str <value>' argument missing",
}
);
// Unallowed positional
assert.throws(
() => parseArgs({
options,
allowPositionals: false, // (the default)
args: ['posarg']
}),
{
name: 'TypeError',
message: "Unexpected argument 'posarg'. " +
"This command does not take positional arguments",
}
);parseArgs トークンparseArgs() は、args 配列を 2 つのフェーズで処理します。
args をトークンの配列に解析します。これらのトークンは、ほとんどの場合、型情報で注釈が付けられた args の要素です。オプションですか? 位置引数ですか? など。ただし、オプションに値がある場合、トークンはオプション名とオプション値の両方を格納するため、2 つの args 要素のデータが含まれます。.values を介して返されるオブジェクトにアセンブルします。config.tokens を true に設定すると、トークンにアクセスできます。次に、parseArgs() によって返されるオブジェクトには、トークンを持つプロパティ .tokens が含まれます。
これらはトークンのプロパティです。
type Token = OptionToken | PositionalToken | OptionTerminatorToken;
interface CommonTokenProperties {
/** Where in `args` does the token start? */
index: number;
}
interface OptionToken extends CommonTokenProperties {
kind: 'option';
/** Long name of option */
name: string;
/** The option name as mentioned in `args` */
rawName: string;
/** The option’s value. `undefined` for boolean options. */
value: string | undefined;
/** Is the option value specified inline (e.g. --level=5)? */
inlineValue: boolean | undefined;
}
interface PositionalToken extends CommonTokenProperties {
kind: 'positional';
/** The value of the positional, args[token.index] */
value: string;
}
interface OptionTerminatorToken extends CommonTokenProperties {
kind: 'option-terminator';
}例として、次のオプションを検討してください。
const options = {
'bool': {
type: 'boolean',
short: 'b',
},
'flag': {
type: 'boolean',
short: 'f',
},
'str': {
type: 'string',
short: 's',
},
};ブールオプションのトークンは次のようになります。
assert.deepEqual(
parseArgs({
options, tokens: true,
args: [
'--bool', '-b', '-bf',
]
}),
{
values: {__proto__:null,
bool: true,
flag: true,
},
positionals: [],
tokens: [
{
kind: 'option',
name: 'bool',
rawName: '--bool',
index: 0,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
name: 'bool',
rawName: '-b',
index: 1,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
name: 'bool',
rawName: '-b',
index: 2,
value: undefined,
inlineValue: undefined
},
{
kind: 'option',
name: 'flag',
rawName: '-f',
index: 2,
value: undefined,
inlineValue: undefined
},
]
}
);args で 3 回言及されているため、オプション bool のトークンが 3 つあることに注意してください。ただし、解析のフェーズ 2 のため、.values に bool のプロパティは 1 つしかありません。
次の例では、文字列オプションをトークンに解析します。.inlineValue には、ブール値が含まれるようになりました(ブールオプションの場合、常に undefined です)。
assert.deepEqual(
parseArgs({
options, tokens: true,
args: [
'--str', 'yes', '--str=yes', '-s', 'yes',
]
}),
{
values: {__proto__:null,
str: 'yes',
},
positionals: [],
tokens: [
{
kind: 'option',
name: 'str',
rawName: '--str',
index: 0,
value: 'yes',
inlineValue: false
},
{
kind: 'option',
name: 'str',
rawName: '--str',
index: 2,
value: 'yes',
inlineValue: true
},
{
kind: 'option',
name: 'str',
rawName: '-s',
index: 3,
value: 'yes',
inlineValue: false
}
]
}
);最後に、位置引数とオプションターミネータの解析例を以下に示します。
assert.deepEqual(
parseArgs({
options, allowPositionals: true, tokens: true,
args: [
'command', '--', '--str', 'yes', '--str=yes'
]
}),
{
values: {__proto__:null,
},
positionals: [ 'command', '--str', 'yes', '--str=yes' ],
tokens: [
{ kind: 'positional', index: 0, value: 'command' },
{ kind: 'option-terminator', index: 1 },
{ kind: 'positional', index: 2, value: '--str' },
{ kind: 'positional', index: 3, value: 'yes' },
{ kind: 'positional', index: 4, value: '--str=yes' }
]
}
);デフォルトでは、parseArgs() は git clone や npm install などのサブコマンドをサポートしていません。ただし、トークンを介してこの機能を比較的簡単に実装できます。
以下に実装を示します。
function parseSubcommand(config) {
// The subcommand is a positional, allow them
const {tokens} = parseArgs({
...config, tokens: true, allowPositionals: true
});
let firstPosToken = tokens.find(({kind}) => kind==='positional');
if (!firstPosToken) {
throw new Error('Command name is missing: ' + config.args);
}
//----- Command options
const cmdArgs = config.args.slice(0, firstPosToken.index);
// Override `config.args`
const commandResult = parseArgs({
...config, args: cmdArgs, tokens: false, allowPositionals: false
});
//----- Subcommand
const subcommandName = firstPosToken.value;
const subcmdArgs = config.args.slice(firstPosToken.index+1);
// Override `config.args`
const subcommandResult = parseArgs({
...config, args: subcmdArgs, tokens: false
});
return {
commandResult,
subcommandName,
subcommandResult,
};
}これが実行中の parseSubcommand() です。
const options = {
'log': {
type: 'string',
},
color: {
type: 'boolean',
}
};
const args = ['--log', 'all', 'print', '--color', 'file.txt'];
const result = parseSubcommand({options, allowPositionals: true, args});
const pn = obj => Object.setPrototypeOf(obj, null);
assert.deepEqual(
result,
{
commandResult: {
values: pn({'log': 'all'}),
positionals: []
},
subcommandName: 'print',
subcommandResult: {
values: pn({color: true}),
positionals: ['file.txt']
}
}
);