javascript
nodejs命令行执行程序_在NodeJS中编写命令行应用程序
nodejs命令行執(zhí)行程序
by Peter Benjamin
彼得·本杰明(Peter Benjamin)
在NodeJS中編寫命令行應(yīng)用程序 (Writing Command-Line Applications in NodeJS)
With the right packages, writing command-line apps in NodeJS is a breeze.
有了合適的軟件包,用NodeJS編寫命令行應(yīng)用程序變得輕而易舉。
One package in particular makes it extremely easy: Commander.
一個(gè)軟件包特別容易: Commander 。
Let’s set the stage and walk-through how to write a command-line interface (CLI) app in NodeJS with Commander. Our goal will be to write a CLI app to list files and directories.
讓我們?cè)O(shè)置階段并逐步講解如何使用Commander在NodeJS中編寫命令行界面(CLI)應(yīng)用程序。 我們的目標(biāo)是編寫一個(gè)CLI應(yīng)用程序以列出文件和目錄。
建立 (Setup)
IDEsI love online IDEs. They abstract away a lot of headaches when it comes to dev environment setup. I personally use Cloud9 for the following reasons:
IDE我喜歡在線IDE。 在開(kāi)發(fā)環(huán)境設(shè)置方面,他們消除了很多麻煩。 我個(gè)人出于以下原因使用Cloud9 :
- The layout is intuitive 布局直觀
- The editor is beautiful and easy-to-use 該編輯器美觀且易于使用
- Free-tier resources have recently been increased to 1GB RAM and 5GB disk space, which is more than plenty for a decent-sized NodeJS application. 自由層資源最近已增加到1GB RAM和5GB磁盤空間,對(duì)于像樣的NodeJS應(yīng)用程序來(lái)說(shuō)已經(jīng)足夠了。
- Unlimited number of workstations 工作站數(shù)量不限
- It’s a perfect environment to test or experiment with new projects/ideas without fear of breaking your environment 這是測(cè)試或試驗(yàn)新項(xiàng)目/想法的理想環(huán)境,而不必?fù)?dān)心破壞您的環(huán)境
Node/NPM VersionAt the time of writing this article, Node is at version 5.3.0 and NPM is ad version 3.3.12.
節(jié)點(diǎn)/ NPM版本在撰寫本文時(shí),Node的版本為5.3.0,NPM的廣告版本為3.3.12。
初始化 (Initialization)
We start by initializing our project, accept all the NPM defaults, and installing the commander package:
我們首先初始化項(xiàng)目,接受所有NPM默認(rèn)值,然后安裝commander軟件包:
npm initnpm i --save commanderResulting in:
導(dǎo)致:
Note:
注意:
You will have to add bin manually, which tells NodeJS what your CLI app is called and what is the entry point to your app.
您將必須手動(dòng)添加bin ,這將告訴NodeJS您的CLI應(yīng)用程序是什么以及應(yīng)用程序的入口點(diǎn)是什么。
- Make sure you do not use a command name that already exists in your system. 確保您不使用系統(tǒng)中已經(jīng)存在的命令名稱。
Index.js (Index.js)
Now that we’ve initialized our project and indicated that our entry point is index.js, let’s create index.js:
現(xiàn)在我們已經(jīng)初始化了項(xiàng)目,并指出我們的入口點(diǎn)是index.js,讓我們創(chuàng)建index.js:
touch index.jsNow, for the actual coding part:
現(xiàn)在,對(duì)于實(shí)際的編碼部分:
Typically, when executing NodeJS files, we tell the system to use the appropriate interpreter by prefixing node before the file. However, we want to be able to execute our CLI app globally from anywhere in the system, and without having to specify the node interpreter every time.
通常,在執(zhí)行NodeJS文件時(shí),我們通過(guò)在文件之前添加節(jié)點(diǎn)前綴來(lái)告訴系統(tǒng)使用適當(dāng)?shù)慕忉屍鳌?但是,我們希望能夠從系統(tǒng)中的任何位置全局執(zhí)行CLI應(yīng)用程序,而不必每次都指定節(jié)點(diǎn)解釋器。
Therefore, our first line is the shebang expression:
因此,我們的第一行是shebang表達(dá)式:
#!/usr/bin/env nodeThis not only tells our system to use the appropriate interpreter, but it also tells our system to use the appropriate version of the interpreter.
這不僅告訴我們的系統(tǒng)使用適當(dāng)?shù)慕忉屍?#xff0c;而且還告訴我們的系統(tǒng)使用適當(dāng)?shù)慕忉屍靼姹?。
From here on out, we write pure JavaScript code.Since I’ll be writing ES6-compliant code, I’ll start with the literal expression:
從現(xiàn)在開(kāi)始,我們將編寫純JavaScript代碼。由于我將編寫符合ES6的代碼,因此將從文字表達(dá)式開(kāi)始:
'use strict';This tells the compiler to use a stricter variant of javascript [1] and enables us to write ES6 code on Cloud9.
這告訴編譯器使用javascript [ 1 ]的更嚴(yán)格的變體,并使我們能夠在Cloud9上編寫ES6代碼。
Let’s start by requiring the commander package:
讓我們從要求commander包開(kāi)始:
const program = require('commander');Now, writing CLI apps with commander is simple, and the documentation is great, but I struggled with a few concepts that I will attempt to clear up here.
現(xiàn)在,使用Commander編寫CLI應(yīng)用程序是 很簡(jiǎn)單,文檔也很棒,但是我在嘗試一些概念時(shí)遇到了麻煩,在這里我將嘗試加以澄清。
There seems to be 2 designs for CLI apps. Take ls and git for example.
CLI應(yīng)用程序似乎有2種設(shè)計(jì)。 以ls和git為例。
With ls, you pass one or more flags:
使用ls ,您傳遞一個(gè)或多個(gè)標(biāo)志:
ls -l
ls -l
ls -al
ls -al
With git, you pass sub-commands, but you also have some flags:
使用git ,您可以傳遞子命令,但也有一些標(biāo)志:
git commit -am <message>
git commit -am <messa ge>
git remote add origin <repo-url>
git remote add origin <repo-u rl>
We will explore the flexibility Commander gives us to design both types of command-line interfaces.
我們將探索Commander給我們?cè)O(shè)計(jì)兩種命令行界面的靈活性。
指揮官API (Commander API)
The Commander API is straight forward and the documentation is great.
Commander API非常簡(jiǎn)單,文檔也很棒。
There are 3 basic ways we can write our program:
我們可以通過(guò)3種基本方法來(lái)編寫程序:
METHOD #1: Flag-only command-line application
方法1:僅標(biāo)志命令行應(yīng)用程序
const program = require('commander');program .version('0.0.1') .option('-o, --option','option description') .option('-m, --more','we can have as many options as we want') .option('-i, --input [optional]','optional user input') .option('-I, --another-input <required>','required user input') .parse(process.argv); // end with parse to parse through the inputNote:
注意:
- The short-hand and long-hand options are in the same string (see the bold text in the image above) 簡(jiǎn)寫和長(zhǎng)寫選項(xiàng)都在同一字符串中(請(qǐng)參見(jiàn)上圖中的粗體文本)
-o and -m will return boolean values when users pass them because we didn’t specify any optional or required user input.
當(dāng)用戶傳遞它們時(shí), -o和-m將返回布爾值,因?yàn)槲覀儧](méi)有指定任何可選或必需的用戶輸入。
-i and -I will capture user input and pass the values to our CLI app.
-i和-I將捕獲用戶輸入并將值傳遞到我們的CLI應(yīng)用程序。
- Any value enclosed in square brackets (e.g. [ ] ) is considered optional. User may or may not provide a value. 方括號(hào)內(nèi)的任何值(例如[])均視為可選值。 用戶可能會(huì)或可能不會(huì)提供值。
- Any value enclosed in angled brackets (e.g. < > ) is considered required. User must provide a value. 包含在尖括號(hào)中的任何值(例如<>)都被認(rèn)為是必需的。 用戶必須提供一個(gè)值。
The example above allows us to implement a flag-only approach to our CLI app. Users will be expected to interact with our app like so:
上面的示例使我們可以對(duì)CLI應(yīng)用程序?qū)嵤﹥H標(biāo)志方法。 預(yù)計(jì)用戶將與我們的應(yīng)用程序交互,如下所示:
//Examples:$ cli-app -om -I hello$ cli-app --option -i optionalValue -I requiredValueMETHOD #2: Sub-command and flag-based command-line application
方法2:子命令和基于標(biāo)志的命令行應(yīng)用程序
const program = require('commander');program .version('0.0.1') .command('command <req> [optional]') .description('command description') .option('-o, --option','we can still have add'l options') .action(function(req,optional){ console.log('.action() allows us to implement the command'); console.log('User passed %s', req); if (optional) { optional.forEach(function(opt){ console.log("User passed optional arguments: %s", opt); }); } });program.parse(process.argv); // notice that we have to parse in a new statement.Note:
注意:
If we utilize .command(‘command…’).description(‘description…’), we must utilize .action() to pass a function and execute our code based on the user’s input. (I point this out because there is an alternative method to utilize .command() that we’ll explore next.)
如果我們使用.command('command ...')。description('description ...') ,則必須利用.action()傳遞函數(shù)并根據(jù)用戶輸入執(zhí)行代碼。 (我指出這一點(diǎn)是因?yàn)槲覀兘酉聛?lái)將探討利用.command()的另一種方法。)
If we utilize .command(‘command…’), we can no longer just tack on .parse(process.argv) at the end like we did in the previous example. We have to pass parse() in a new statement
如果我們使用.command('command ...') ,那么我們將不再像前面的示例那樣最后僅附加.parse(process.argv) 。 我們必須在新語(yǔ)句中傳遞parse()
Users are expected to interact with our CLI app like so:
希望用戶與我們的CLI應(yīng)用程序進(jìn)行交互,如下所示:
//Example: $ cli-app command requiredValue -oMETHOD #3: Same as METHOD #2 above, but allows for modularized code
方法3:與上述方法2相同,但允許使用模塊化代碼
Finally, we don’t have to bloat our one JavaScript file with all the .command().description().action() logic. We can modularize our CLI project like so:
最后,我們不必使用所有.command()。description()。action()邏輯來(lái)膨脹一個(gè)JavaScript文件。 我們可以像這樣對(duì)CLI項(xiàng)目進(jìn)行模塊化:
// file: ./cli-app/index.jsconst program = require('commander');program.version('0.0.1').command('command <req> [optional]','command description').command('command2','command2 description').command('command3','command3 description').parse(process.argv);Note:
注意:
If we utilize .command(‘command’, ‘description’) to pass in the command and the description, we can no longer have .action(). Commander will imply that we have separate files with a specific naming convention where we can handle each command. The naming convention is index-command.js, index-command2.js, index-command3.js. See examples of this on Github (specifically: pm, pm-install, pm-publish files).
如果我們利用.command('command','description')傳遞命令和描述,我們將不再擁有.action()。 Commander表示我們有單獨(dú)的文件,具有特定的命名約定,可以在其中處理每個(gè)命令。 命名約定為index-command.js , index-command2.js , index-command3.js 。 在Github上查看此示例 (具體是: pm , pm-install , pm-publish文件)。
If we take this route, we can just tack on .parse() at the end.
如果采用這種方式,則只需在最后添加.parse()即可 。
回到我們的項(xiàng)目場(chǎng)景… (Back to our project scenario…)
Now that we’ve covered the basics, it’s all downhill from here. We can take a deep breath.
既然我們已經(jīng)介紹了基礎(chǔ)知識(shí),那么一切都從這里開(kāi)始。 我們可以深吸一口氣。
*** SIGH ***
*** SIGH ***
All right, now the fun begins.
好吧,現(xiàn)在開(kāi)始樂(lè)趣了。
If we recall our project scenario, we want to write a CLI app to list files and directories. So let’s start writing the code.
如果我們回想起我們的項(xiàng)目場(chǎng)景,我們想編寫一個(gè)CLI應(yīng)用程序以列出文件和目錄。 因此,讓我們開(kāi)始編寫代碼。
We want to give the user the ability to decide if they want to see “all” files (including hidden ones) and/or if they want to see the long listing format (including the rights/permissions of the files/folders).
我們希望使用戶能夠決定是否要查看“所有”文件(包括隱藏文件)和/或是否要查看長(zhǎng)列表格式(包括文件/文件夾的權(quán)限/許可)。
So, we start by writing the basic shell of our program to see our incremental progress (we will follow signature of Method #2 for the sake of the demo) :
因此,我們首先編寫程序的基本外殼以查看增量進(jìn)度(為了演示,我們將遵循方法2的簽名):
#!/usr/bin/env node'use strict';const program = require('commander');program .version('') .command('') .description('') .option('','') .option('','') .action('');program.parse(process.argv);Let’s start filling the blanks:
讓我們開(kāi)始填補(bǔ)空白:
#!/usr/bin/env node'use strict';const program = require('commander');program .version('0.0.1') .command('list [directory]') .description('List files and folders') .option('-a, --all','List all files and folders') .option('-l, --long','') .action();program.parse(process.argv);Note:
注意:
We decided to name our command list.
我們決定命名命令列表 。
Directory argument is optional, so user can simply ignore to pass a directory, in which case we will list files/folders in current directory.
目錄 參數(shù)是可選的,因此用戶可以忽略傳遞目錄,在這種情況下,我們將在當(dāng)前目錄中列出文件/文件夾。
So, in our scenario, the following calls are valid:
因此,在我們的方案中,以下調(diào)用是有效的:
$ cli-app list $ cli-app list -al$ cli-app list --all$ cli-app list --long$ cli-app list /home/user -alNow, let’s start writing code for our .action().
現(xiàn)在,讓我們開(kāi)始為.action()編寫代碼。
#!/usr/bin/env node'use strict';const program = require('commander');let listFunction = (directory,options) => { //some code here}program .version('0.0.1') ... .action(listFunction);program.parse(process.argv);We are going to cheat here by using the built-in ls command that’s available in all unix-like operating systems.
我們將使用所有類unix操作系統(tǒng)中可用的內(nèi)置ls命令來(lái)作弊。
#!/usr/bin/env node'use strict';const program = require('commander'), exec = require('child_process').exec;let listFunction = (directory,options) => {const cmd = 'ls';let params = [];if (options.all) params.push('a');if (options.long) params.push('l');let fullCommand = params.length ? cmd + ' -' + params.join('') : cmdif (directory) fullCommand += ' ' + directory;};program .version('0.0.1') ... .action(listFunction);program.parse(process.argv);Let’s talk reason about this code.
讓我們談?wù)勥@段代碼的原因。
First, we require the child_process library to execute shell commands* (*this opens up a big security risk that I will discuss later)
首先,我們要求child_process庫(kù)執(zhí)行shell命令* ( * 這帶來(lái)了很大的安全風(fēng)險(xiǎn),我將在后面討論 )
We declare an array that will hold any parameters passed by the user (e.g. -a, -l)
我們聲明一個(gè)數(shù)組,該數(shù)組將保存用戶傳遞的所有參數(shù)(例如-a , -l )
We check to see whether the user passed short-hand (-a) or long-hand ( — all) flags. If so, then options.all and/or options.long will evaluate to true, in which case we will push the respective command flag to our array. Our goal is to build the shell command that we will pass later to child_process.
我們檢查用戶是否通過(guò)了短手( -a )或長(zhǎng)手(— all )標(biāo)志。 如果是這樣,則options.all和/或options.long的評(píng)估結(jié)果為true ,在這種情況下,我們會(huì)將相應(yīng)的命令標(biāo)志壓入數(shù)組。 我們的目標(biāo)是構(gòu)建將在以后傳遞給child_process的shell命令。
Finally, we check if user passed any optional directory values. If so, we concatenate it to the fully constructed command.
最后,我們檢查用戶是否傳遞了任何可選的目錄值。 如果是這樣,我們將其連接到完全構(gòu)造的命令。
Now, we want to execute the fully constructed command in the shell. Child_Process.exec() gives us the ability to do so and NodeJS docs give us the signature:
現(xiàn)在,我們要在shell中執(zhí)行完全構(gòu)造的命令。 Child_Process.exec()使我們能夠這樣做,而NodeJS文檔給我們簽名:
child_process.exec(command, callback(error, stdout, stderr){ //"error" will be returned if exec encountered an error. //"stdout" will be returned if exec is successful and data is returned. //"stderr" will be returned if the shell command encountered an error.})So, let’s use this:
所以,讓我們使用這個(gè):
#!/usr/bin/env node'use strict';const program = require('commander'), exec = require('child_process').exec;let listFunction = (directory,options) => { const cmd = 'ls'; let params = []; if (options.all) params.push('a'); if (options.long) params.push('l'); let fullCommand = params.length ? cmd + ' -' + params.join('') : cmd if (directory) fullCommand += ' ' + directory;let execCallback = (error, stdout, stderr) => { if (error) console.log("exec error: " + error); if (stdout) console.log("Result: " + stdout); if (stderr) console.log("shell error: " + stderr); };exec(fullCommand, execCallback);};program .version('0.0.1') ... .action(listFunction);program.parse(process.argv);That’s it!
而已!
Here is the gist of my sample CLI app.
這是我的示例CLI應(yīng)用程序的要點(diǎn) 。
Of course, we can add a few niceties, like:
當(dāng)然,我們可以添加一些細(xì)節(jié),例如:
Coloring the output (I use the chalk library below)
著色輸出(我使用下面的粉筆庫(kù))
Modern CLI apps are smart enough to show the help text when a user calls the program with no parameters or arguments (much like git), so I added that functionality at the very bottom.
現(xiàn)代的CLI應(yīng)用程序足夠聰明,可以在用戶不帶任何參數(shù)或參數(shù)(類似于git )的情況下調(diào)用該程序時(shí)顯示幫助文本,因此我在最底部添加了該功能。
Finally, we can take advantage of NPM to symbolic link our CLI application so we can use it globally in our system. Simply, in the terminal, cd into the root of our CLI app and type:
最后,我們可以利用NPM來(lái)符號(hào)鏈接CLI應(yīng)用程序,以便可以在系統(tǒng)中全局使用它。 只需在終端中,將cd插入我們的CLI應(yīng)用程序的根目錄,然后鍵入:
npm link最后的想法和考慮 (Final thoughts & Considerations)
The code in this project is by no means the best code. I am fully aware that there is always room for improvement, so feedback is welcome!
這個(gè)項(xiàng)目中的代碼絕不是最好的代碼。 我完全意識(shí)到,總會(huì)有改進(jìn)的余地,因此歡迎反饋!
Also, I want to point out a security flaw in our app. Our code does not sanitize or validate the users’ input. This violates security best practices. Consider the following scenarios where users can pass un-desired input:
另外,我想指出我們應(yīng)用程序中的安全漏洞。 我們的代碼不清除或驗(yàn)證用戶的輸入。 這違反了安全性最佳做法。 請(qǐng)考慮以下情況,用戶可以傳遞不需要的輸入:
$ cli-app -al ; rm -rf /$ cli-app -al ; :(){ :|: & };:If you want to write some code that handles this issue, or fixes any other potential issues, be sure to show us your code by leaving a comment.
如果您想編寫一些處理此問(wèn)題的代碼,或者解決任何其他潛在問(wèn)題,請(qǐng)確保通過(guò)評(píng)論向我們展示您的代碼。
翻譯自: https://www.freecodecamp.org/news/writing-command-line-applications-in-nodejs-2cf8327eee2/
nodejs命令行執(zhí)行程序
總結(jié)
以上是生活随笔為你收集整理的nodejs命令行执行程序_在NodeJS中编写命令行应用程序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: web项目开发人员配比_我如何找到Web
- 下一篇: 梦到塔倒了什么兆头