nodejs命令行执行程序_在NodeJS中编写命令行应用程序

nodejs命令行执行程序

by Peter Benjamin

彼得·本杰明(Peter Benjamin)

在NodeJS中编写命令行应用程序 (Writing Command-Line Applications in NodeJS)

With the right packages, writing command-line apps in NodeJS is a breeze.

有了合适的软件包,用NodeJS编写命令行应用程序变得轻而易举。

One package in particular makes it extremely easy: Commander.

一个软件包特别容易: 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.

让我们设置阶段并逐步讲解如何使用Commander在NodeJS中编写命令行界面(CLI)应用程序。 我们的目标是编写一个CLI应用程序以列出文件和目录。

建立 (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。 在开发环境设置方面,他们消除了很多麻烦。 我个人出于以下原因使用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磁盘空间,对于像样的NodeJS应用程序来说已经足够了。
  • Unlimited number of workstations

    工作站数量不限
  • It’s a perfect environment to test or experiment with new projects/ideas without fear of breaking your environment

    这是测试或试验新项目/想法的理想环境,而不必担心破坏您的环境

Node/NPM VersionAt the time of writing this article, Node is at version 5.3.0 and NPM is ad version 3.3.12.

节点/ NPM版本在撰写本文时,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:

我们首先初始化项目,接受所有NPM默认值,然后安装commander软件包:

npm initnpm i --save commander

Resulting in:

导致:

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.

    您将必须手动添加bin ,这将告诉NodeJS您的CLI应用程序是什么以及应用程序的入口点是什么。

  • Make sure you do not use a command name that already exists in your system.

    确保您不使用系统中已经存在的命令名称。
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:

现在我们已经初始化了项目,并指出我们的入口点是index.js,让我们创建index.js:

touch index.js

Now, for the actual coding part:

现在,对于实际的编码部分:

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.

通常,在执行NodeJS文件时,我们通过在文件之前添加节点前缀来告诉系统使用适当的解释器。 但是,我们希望能够从系统中的任何位置全局执行CLI应用程序,而不必每次都指定节点解释器。

Therefore, our first line is the shebang expression:

因此,我们的第一行是shebang表达式:

#!/usr/bin/env node

This not only tells our system to use the appropriate interpreter, but it also tells our system to use the appropriate version of the interpreter.

这不仅告诉我们的系统使用适当的解释器,而且还告诉我们的系统使用适当的解释器版本

From here on out, we write pure JavaScript code.Since I’ll be writing ES6-compliant code, I’ll start with the literal expression:

从现在开始,我们将编写纯JavaScript代码。由于我将编写符合ES6的代码,因此将从文字表达式开始:

'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 ]的更严格的变体,并使我们能够在Cloud9上编写ES6代码。

Let’s start by requiring the commander package:

让我们从要求commander包开始:

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.

现在,使用Commander编写CLI应用程序是 很简单,文档也很棒,但是我在尝试一些概念时遇到了麻烦,在这里我将尝试加以澄清。

There seems to be 2 designs for CLI apps. Take ls and git for example.

CLI应用程序似乎有2种设计。 以lsgit为例。

With ls, you pass one or more flags:

使用ls ,您传递一个或多个标志:

  • ls -l

    ls -l

  • ls -al

    ls -al

With git, you pass sub-commands, but you also have some flags:

使用git ,您可以传递子命令,但也有一些标志:

  • 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给我们设计两种命令行界面的灵活性。

指挥官API (Commander API)

The Commander API is straight forward and the documentation is great.

Commander API非常简单,文档也很棒。

There are 3 basic ways we can write our program:

我们可以通过3种基本方法来编写程序:

METHOD #1: Flag-only command-line application

方法1:仅标志命令行应用程序

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 input

Note:

注意:

  • The short-hand and long-hand options are in the same string (see the bold text in the image above)

    简写和长写选项都在同一字符串中(请参见上图中的粗体文本)
  • -o and -m will return boolean values when users pass them because we didn’t specify any optional or required user input.

    当用户传递它们时, -o-m将返回布尔值,因为我们没有指定任何可选必需的用户输入。

  • -i and -I will capture user input and pass the values to our CLI app.

    -i-I将捕获用户输入并将值传递到我们的CLI应用程序。

  • Any value enclosed in square brackets (e.g. [ ] ) is considered optional. User may or may not provide a value.

    方括号内的任何值(例如[])均视为可选值。 用户可能会或可能不会提供值。
  • Any value enclosed in angled brackets (e.g. < > ) is considered required. User must provide a value.

    包含在尖括号中的任何值(例如<>)都被认为是必需的。 用户必须提供一个值。

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:

上面的示例使我们可以对CLI应用程序实施仅标志方法。 预计用户将与我们的应用程序交互,如下所示:

//Examples:$ cli-app -om -I hello$ cli-app --option -i optionalValue -I requiredValue

METHOD #2: Sub-command and flag-based command-line application

方法2:子命令和基于标志的命令行应用程序

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()传递函数并根据用户输入执行代码。 (我指出这一点是因为我们接下来将探讨利用.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) 。 我们必须在新语句中传递parse()

Users are expected to interact with our CLI app like so:

希望用户与我们的CLI应用程序进行交互,如下所示:

//Example: $ cli-app command requiredValue -o

METHOD #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()逻辑来膨胀一个JavaScript文件。 我们可以像这样对CLI项目进行模块化:

// 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表示我们有单独的文件,具有特定的命名约定,可以在其中处理每个命令。 命名约定为index-command.jsindex-command2.jsindex-command3.js在Github上查看此示例 (具体是: pmpm-installpm-publish文件)。

  • If we take this route, we can just tack on .parse() at the end.

    如果采用这种方式,则只需在最后添加.parse()即可

回到我们的项目场景… (Back to our project scenario…)

Now that we’ve covered the basics, it’s all downhill from here. We can take a deep breath.

既然我们已经介绍了基础知识,那么一切都从这里开始。 我们可以深吸一口气。

*** SIGH ***

*** SIGH ***

All right, now the fun begins.

好吧,现在开始乐趣了。

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.

如果我们回想起我们的项目场景,我们想编写一个CLI应用程序以列出文件和目录。 因此,让我们开始编写代码。

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).

我们希望使用户能够决定是否要查看“所有”文件(包括隐藏文件)和/或是否要查看长列表格式(包括文件/文件夹的权限/许可)。

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) :

因此,我们首先编写程序的基本外壳以查看增量进度(为了演示,我们将遵循方法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:

让我们开始填补空白:

#!/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.

    目录 参数是可选的,因此用户可以忽略传递目录,在这种情况下,我们将在当前目录中列出文件/文件夹。

So, in our scenario, the following calls are valid:

因此,在我们的方案中,以下调用是有效的:

$ cli-app list $ cli-app list -al$ cli-app list --all$ cli-app list --long$ cli-app list /home/user -al

Now, let’s start writing code for our .action().

现在,让我们开始为.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操作系统中可用的内置ls命令来作弊。

#!/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.

让我们谈谈这段代码的原因。

  1. 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库执行shell命令* ( * 这带来了很大的安全风险,我将在后面讨论 )

  2. We declare a constant variable that holds the root of our command

    我们声明一个常量变量,该变量保存命令的根
  3. We declare an array that will hold any parameters passed by the user (e.g. -a, -l)

    我们声明一个数组,该数组将保存用户传递的所有参数(例如-a-l )

  4. 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.

    我们检查用户是否通过了短手( -a )或长手(— all )标志。 如果是这样,则options.all和/或options.long的评估结果为true ,在这种情况下,我们会将相应的命令标志压入数组。 我们的目标是构建将在以后传递给child_process的shell命令。

  5. We declare a new variable to hold the full shell command. If the param array contains any flags, we concatenate the flags to each other and to the root command. Otherwise, if param array is empty, then we use the root command as is.

    我们声明一个新变量来保存完整的shell命令。 如果param数组包含任何标志,我们将这些标志相互连接并与root命令连接。 否则,如果param数组为空,则按原样使用root命令。
  6. Finally, we check if user passed any optional directory values. If so, we concatenate it to the fully constructed command.

    最后,我们检查用户是否传递了任何可选的目录值。 如果是这样,我们将其连接到完全构造的命令。

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:

现在,我们要在shell中执行完全构造的命令。 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:

所以,让我们使用这个:

#!/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应用程序的要点

Of course, we can add a few niceties, like:

当然,我们可以添加一些细节,例如:

  • Coloring the output (I use the chalk library below)

    着色输出(我使用下面的粉笔库)

  • 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.

    现代的CLI应用程序足够聪明,可以在用户不带任何参数或参数(类似于git )的情况下调用该程序时显示帮助文本,因此我在最底部添加了该功能。

#!/usr/bin/env node'use strict';/** * Require dependencies * */const program = require('commander'),    chalk = require("chalk"),    exec = require('child_process').exec,    pkg = require('./package.json');/** * list function definition * */let list = (directory,options)  => {    const cmd = 'ls';    let params = [];        if (options.all) params.push("a");    if (options.long) params.push("l");    let parameterizedCommand = params.length                                 ? cmd + ' -' + params.join('')                                 : cmd ;    if (directory) parameterizedCommand += ' ' + directory ;        let output = (error, stdout, stderr) => {        if (error) console.log(chalk.red.bold.underline("exec error:") + error);        if (stdout) console.log(chalk.green.bold.underline("Result:") + stdout);        if (stderr) console.log(chalk.red("Error: ") + stderr);    };        exec(parameterizedCommand,output);    };program    .version(pkg.version)    .command('list [directory]')    .option('-a, --all', 'List all')    .option('-l, --long','Long list format')    .action(list);program.parse(process.argv);// if program was called with no arguments, show help.if (program.args.length === 0) program.help();

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来符号链接CLI应用程序,以便可以在系统中全局使用它。 只需在终端中,将cd插入我们的CLI应用程序的根目录,然后键入:

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!

这个项目中的代码绝不是最好的代码。 我完全意识到,总会有改进的余地,因此欢迎反馈!

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:

另外,我想指出我们应用程序中的安全漏洞。 我们的代码不清除验证用户的输入。 这违反了安全性最佳做法。 请考虑以下情况,用户可以传递不需要的输入:

$ 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.

如果您想编写一些处理此问题的代码,或者解决任何其他潜在问题,请确保通过评论向我们展示您的代码。

翻译自: https://www.freecodecamp.org/news/writing-command-line-applications-in-nodejs-2cf8327eee2/

nodejs命令行执行程序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值