<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Node.js 中如何收集和解析命令行參數(shù)

          共 1563字,需瀏覽 4分鐘

           ·

          2021-02-02 09:30

          前言

          ??在開發(fā) CLI(Command Line Interface)工具的業(yè)務(wù)場(chǎng)景下,離不開命令行參數(shù)的收集和解析。

          ??接下來,本文介紹如何收集和解析命令行參數(shù)。

          收集命令行參數(shù)

          ??在 Node.js 中,可以通過 process.argv 屬性收集進(jìn)程被啟動(dòng)時(shí)傳入的命令行參數(shù):

          ??//?./example/demo.js
          ??process.argv.slice(2);

          ??//?命令行執(zhí)行如下命令
          ??node?./example/demo.js?--name=xiaoming?--age=20?man

          ??//?得到的結(jié)果
          ??[?'--name=xiaoming',?'--age=20',?'man'?]

          ??由上述示例可以發(fā)現(xiàn),Node.js 在處理命令行參數(shù)時(shí),只是簡(jiǎn)單地通過空格來分割字符串。

          ??對(duì)于這樣的參數(shù)數(shù)組,無(wú)法很方便地獲取到每個(gè)參數(shù)對(duì)應(yīng)的值,所以需要再進(jìn)行一次解析操作。

          命令行參數(shù)風(fēng)格

          ??在解析命令行參數(shù)之前,需要了解一些常見的命令行參數(shù)風(fēng)格:

          • Unix 風(fēng)格:參數(shù)以「-」(連字符)開頭
          • GNU 風(fēng)格:參數(shù)以「--」(雙連字符)開頭
          • BSD 風(fēng)格:參數(shù)以空格分割

          ??Unix 參數(shù)風(fēng)格有一個(gè)特殊的注意事項(xiàng):「「-」后面緊鄰的每一個(gè)字母都表示一個(gè)參數(shù)名」

            ls -al

          ??上述命令用來顯示當(dāng)前目錄下所有的文件、文件夾并且顯示它們的詳細(xì)信息,等同于:

            ls -a -l

          ??GNU 風(fēng)格的參數(shù)以 「--」開頭,一般后面會(huì)跟上一個(gè)單詞或者短語(yǔ),例如熟悉的 npm 安裝依賴的命令:

            npm install --save koa

          對(duì)于兩個(gè)單詞的情況,在 GNU 參數(shù)風(fēng)格中,會(huì)通過「-」來連接,例如 npm 安裝僅用于開發(fā)環(huán)境的依賴:

            npm install --save-dev webpack

          ??BSD 是加州大學(xué)伯克利分校開發(fā)的一個(gè) Unix 版本。其與 Unix 的區(qū)別主要在于參數(shù)前面沒有 「-」,個(gè)人感覺這樣很難區(qū)別參數(shù)和參數(shù)值。

          ?

          注意事項(xiàng):-- 后面緊鄰空格時(shí),表示后面的字符串不需要解析。

          ?

          解析命令行參數(shù)

          function?parse(args?=?[])?{
          ??//?_?屬性用來保留不需要處理的參數(shù)字符串
          ??const?output?=?{?_:?[]?};

          ??for?(let?index?=?0;?index?????const?arg?=?args[index];
          ????
          ????if?(isIgnoreFollowingParameters(output,?args,?index,?arg))?{
          ??????break;
          ????}
          ????
          ????if?(!isParameter(arg))?{
          ??????output._.push(arg);
          ??????continue;
          ????}

          ????...
          ??}

          ??return?output;
          }

          parse(process.argv.slice(2));

          ??接收到命令行參數(shù)數(shù)組之后,需要遍歷數(shù)組,處理每一個(gè)參數(shù)字符串。

          ??isIgnoreFollowingParameters 方法主要用來判斷單個(gè)「--」的場(chǎng)景,后續(xù)的參數(shù)字符串不再需要處理:

          function?isIgnoreFollowingParameters(output,?args,?index,?arg)?{
          ??if?(arg?!==?'--')?{
          ????return?false;
          ??}
          ??output._?=?output._.concat(args.slice(++index));
          ??return?true;
          }

          ??接下來,如果參數(shù)字符串不以「-」開頭,同樣也不需要處理,參數(shù)的形式以 Unix 和 GNU 風(fēng)格為主:

          function?isParameter(arg)?{
          ??return?arg.startsWith('-');
          }

          ??參數(shù)的表現(xiàn)形式主要分為以下幾種:

          • "--name=xiaoming": 參數(shù)名為 name,參數(shù)值為 xiaoming
          • "-abc=10": 參數(shù)名為 a,參數(shù)值為 true;參數(shù)名為 b,參數(shù)值為 true;參數(shù)名為 c,參數(shù)值為 10
          • "--save-dev": 參數(shù)名為 save-dev,參數(shù)值為 true
          • "--age 20":參數(shù)名為 age,參數(shù)值為 20
          ??let?hyphensIndex;
          ??for?(hyphensIndex?=?0;?hyphensIndex?????if?(arg.charCodeAt(hyphensIndex)?!==?45)?{
          ??????break;
          ????}
          ??}

          ??let?assignmentIndex;
          ??for?(assignmentIndex?=?hyphensIndex?+?1;?assignmentIndex?????if?(arg[assignmentIndex].charCodeAt(0)?===?61)?{
          ??????break;
          ????}
          ??}

          ??利用 Unicode 碼點(diǎn)值找出連字符和等號(hào)的下標(biāo)值,從而根據(jù)下標(biāo)分割出參數(shù)名和參數(shù)值:

          ??const?name?=?arg.substring(hyphensIndex,?assignmentIndex);

          ??let?value;
          ??const?assignmentValue?=?arg.substring(++assignmentIndex);

          ??處理參數(shù)值時(shí),需要考慮參數(shù)賦值的四種場(chǎng)景:

          ??if?(assignmentValue)?{
          ????value?=?assignmentValue;?//?--name=xiaoming?or?-abc=10
          ??}?else?if?(index?+?1?===?args.length)?{
          ????value?=?true;?//?--save-dev
          ??}?else?if?((''?+?args[index?+?1]).charCodeAt(0)?!==?45)?{
          ????value?=?args[++index];?//?--age?20
          ??}?else?{
          ????value?=?true;?//?缺省情況
          ??}

          ??由于 Unix 風(fēng)格中每一個(gè)字母都代表一個(gè)參數(shù),并且「手動(dòng)傳遞的參數(shù)值應(yīng)該賦值給最后一個(gè)參數(shù)」,所以還需針對(duì)該場(chǎng)景進(jìn)行適配:

          ??//?「-」or「--」
          ??const?arr?=?hyphensIndex?===?2???[name]?:?name;
          ??for?(let?keyIndex?=?0;?keyIndex?????const?_key?=?arr[keyIndex];
          ????const?_value?=?keyIndex?+?1?????handleKeyValue(output,?_key,?_value);
          ??}

          ??最后針對(duì)參數(shù)的賦值操作,需要考慮到「多次賦值」的情況:

          function?handleKeyValue(output,?key,?value)?{
          ??const?oldValue?=?output[key];
          ??if?(Array.isArray(oldValue))?{
          ????output[key]?=?oldValue.concat(value);
          ????return;
          ??}

          ??if?(oldValue)?{
          ????output[key]?=?[oldValue,?value];
          ????return;
          ??}

          ??output[key]?=?value;
          }

          ??到此,命令行參數(shù)的解析功能就完成了,上述方法執(zhí)行的效果如下:

          ??#?命令行執(zhí)行
          ??node?./example/step1.js?--name=xiaoming?--age?20?--save-dev?-abc=10?-c=20??--?--ignore

          ??#?解析結(jié)果
          ??{
          ????_:?[?'--ignore'?],
          ????name:?'xiaoming',
          ????age:?'20',
          ????'save-dev':?true,
          ????a:?true,
          ????b:?true,
          ????c:?[?'10',?'20'?]
          ??}

          別名機(jī)制

          ??比較優(yōu)秀的 CLI 工具在參數(shù)的解析上都支持參數(shù)的別名設(shè)置,例如使用 npm 安裝開發(fā)環(huán)境依賴時(shí),你可以選擇這種完整的寫法:

            npm install --save-dev webpack

          ??你也可以使用下面這種別名方式:

            npm install -D webpack

          ??從使用上來說 -D 和 --save-dev 是兩種方式,但是從 CLI 工具的開發(fā)者來說,最終處理邏輯時(shí)只能以一個(gè)參數(shù)名為標(biāo)準(zhǔn),所以對(duì)于一個(gè)命令行參數(shù)解析庫(kù)來說,其結(jié)果需要包含所有的情況:

          ??npm?install?--save-dev?webpack

          ??#?解析的結(jié)果
          ??{?'save-dev':?true,?'D':?true?}

          ??以上文的解析方法為例,需要添加額外的選項(xiàng)參數(shù),加入 alias 屬性來聲明別名屬性的對(duì)應(yīng)關(guān)系:

          ??parse(process.argv.slice(2),?{
          ????alias:?{
          ??????'save-dev':?'S'
          ????}
          ??})

          ??上述方式符合正常的理解:設(shè)置參數(shù)對(duì)應(yīng)的別名。但這是一個(gè)「單向查找關(guān)系」,需要轉(zhuǎn)化為:

          ??"alias":?{
          ????"save-dev":?["s"],
          ????"s":?["save-dev"]
          ??}

          ??因?yàn)閷?duì)于使用者來說,只會(huì)選擇一種方式傳遞參數(shù)。對(duì)于開發(fā)者的話需要根據(jù)任意一個(gè)別名找到其相關(guān)聯(lián)的別名:

          function?parse(args?=?[],?options?=?{})?{
          ??const?output?=?{?_:?[]?};

          ??const?{?alias?}?=?options;

          ??const?hasAlias?=?alias?!==?void?666;

          ??if?(hasAlias)?{
          ????Object.keys(alias).forEach(key?=>?{
          ??????alias[key]?=?toArr(alias[key]);
          ??????alias[key].forEach((item,?index)?=>?{
          ????????(alias[item]?=?alias[key].concat(key)).splice(index,?1);
          ??????})
          ????})
          ??}

          ??//?省略解析代碼
          ??...

          ?if?(hasAlias)?{
          ????Object.keys(output).forEach(key?=>?{
          ??????const?arr?=?alias[key]?||?[];
          ??????arr.forEach(sub?=>?output[sub]?=?output[key])
          ????})
          ?}

          ??return?output;
          }

          ??除了別名之外,還可以在參數(shù)解析之后做如下優(yōu)化:

          • 參數(shù)值的類型約束
          • 參數(shù)的默認(rèn)值設(shè)定

          成熟的解析庫(kù)

          ??針對(duì)一些成熟的命令行參數(shù)解析庫(kù)可以采用基準(zhǔn)測(cè)試查看它們的解析效率:

          const?nopt?=?require('nopt');
          const?mri?=?require('mri');
          const?yargs?=?require('yargs-parser');
          const?minimist?=?require('minimist');
          const?{?Suite?}?=?require('benchmark');

          const?bench?=?new?Suite();
          const?args?=?['--name=xiaoming',?'-abc',?'10',?'--save-dev',?'--age',?'20'];

          bench
          ?.add('minimist?????',?()?=>?minimist(args))
          ?.add('mri??????????',?()?=>?mri(args))
          ?.add('nopt?????????',?()?=>?nopt(args))
          ?.add('yargs-parser?',?()?=>?yargs(args))
          ?.on('cycle',?e?=>?console.log(String(e.target)))
          ?.run();

          ??本文的內(nèi)容主要參考解析效率最高的 mri 庫(kù)的源碼,感興趣的同學(xué)可以學(xué)習(xí)其源碼實(shí)現(xiàn)。(順便吐槽一下:嵌套三元操作符可讀性真的很差。。)

          ??雖然上述基準(zhǔn)測(cè)試中 minimist 效率并不很好,但是其覆蓋了比較全的參數(shù)輸入場(chǎng)景。(以上測(cè)試用例覆蓋的場(chǎng)景有限)

          ??愛心三連擊

          1.看到這里了就點(diǎn)個(gè)在看支持下吧,你的點(diǎn)贊,在看是我創(chuàng)作的動(dòng)力。

          2.關(guān)注公眾號(hào)程序員成長(zhǎng)指北,回復(fù)「1」加入高級(jí)前端交流群!「在這里有好多 前端?開發(fā)者,會(huì)討論?前端 Node 知識(shí),互相學(xué)習(xí)」!

          3.也可添加微信【ikoala520】,一起成長(zhǎng)。

          “在看轉(zhuǎn)發(fā)”是最大的支持

          瀏覽 44
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天日天天舔天天爽天天操 | 国产精品一级三级 | 亚洲天堂色在线 | 久久久免费黄色视频 | 语音先锋成人片 |