用Node.js创建命令行工具
发布在每天学点javascript2014年6月7日view:15025
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

enter image description here

用Node.js创建命令行工具

Node.js中一个常常被忽视的功能是创建一个命令行工具。在本文中,我们将一起来一起学习如何使用node.js轻松的创建一个命令行工具。我们将一起来创建一个根据关键词、作者以及语言来快速搜索Github仓库的命令行工具。

理解命令行

无论你使用的是何种语言,在编写Unix风格的命令行工具之前,非常重要的一点是理解输入的基本模式。最基本的输入模式包含三个部分:命令,选项和参数。

命令

命令又分为三种类型:

  1. 内在类型 – 一个不依赖于任何外部可执行文件的命令
  2. 包含类型 - 一个总是需要一个分离的可执行文件的命令,这个可执行文件总是包含在操作系统以及操作系统的重要部分之中
  3. 外部类型 - 一个总是需要一个外部的可执行文件的命令,这个可执行文件不是操作系统的一部分,一般来说由第三方添加

选项

命令行选项可以用来修改一条命令的操作。Unix操作系统中的选项经常会带有连字符并且由空格来区分。

参数

一个命令行参数就是在命令行运行时传递的一条信息。参数通常被用于指定信息的来源,或者用于改变命令的操作。

创建一个命令行工具

在正式编写这个命令行工具之前,需要说明的一点是本文中所有的代码都在OSX系统中进行编写和测试。对于其他的操作系统来说能否正常运行还需要进行一些深入的研究。

开始编写之前需要确认的一件事情是你已经安装了Node.js。你可以在命令行中运行which node来确认是否已经安装。如果你已经安装了node,你可以看到类似于下面的输出结果:

$ which node
/usr/local/bin/node  

首先,创建一个叫做gitsearch.js的文件并在第一行加上shebang。这条信息指明了系统在解释并运行我们的文件时使用的解释器。在我们的例子中,我们需要使用node作为解释器。

#!/usr/bin/env node  

你的代码需要“可执行”(因此它可以通过程序加载器运行)。为了确保它是可执行的,运行chmod +x gitsearch.js命令,这条命令将修改你的脚本的访问许可方式,以便于程序加载器能够执行它。

创建命令

最简单的创建命令的方法是通过可执行文件的文件路径和文件名来进行调用。

./gitsearch.js  

在编写命令行工具时的一个最佳实践方式是确保你的系统中不存在名字相同的命令行工具。这可以通过使用which命令来完成,例如which commandName。在这里的例子中,我们将使用命令gitsearch,因此如果which gitsearch命令返回空白那么说明这个命令名称还没有被使用。

因为这是一个Node脚本,因此我们将使用npm来安装这个脚本,这也是编写Node代码时最常用的工具。这样做的一大好处就是你可以在任何地方使用你的命令。

为了确保能够通过npm成功安装一个Node脚本,我们需要创建一个相应的package.json文件。

{
    "name": "gitsearch",
    "version": "0.0.1",

    "description": "A simple command-line tool for searching git repositories",
    "author": "Glynn Phillips",
    "engines": {
    "node": ">=0.10"
    },
    "dependencies": {
    },
    "bin": {
    "gitsearch": "gitsearch.js"
    }
}

其中最重要的字段是"bin": {"gitsearch": "gitsearch.js"},这个字段将gitsearch命令映射到了你的gitsearch.js脚本。接下来,我们同构命令行来全局安装你的脚本。

cd ./path/to/directory/
sudo npm install -g

这样做的唯一一个缺点是在你每次修改gitsearch.js之后你需要再次运行npm install -g命令来查看发生的变化。

现在运行gitsearch命令,你的脚本将会开始执行。为了测试你的脚本正常运行,在其中加入console.log("Hello World"),并再次运行nom insatll -g然后再次运行gitsearch.js命令。

选项和参数

命令行工具在处理输入输出操作的时候非常有用。选项和参数可以通过process.argv来进行传递。在你的脚本中添加console.log(process.argv);并在你的命令后面加上一个参数,返回结果可能如下所示:

gitsearch -g
[ 'node', '/path/to/script/gitsearch.js', '-g' ]

使用Node开发的一大好处就是社区的开发者上传了很多包和模块。通常来说这些包都很轻量级并且只针对于一个任务。其中一个很好的例子就是Commander,它的作用帮助我们创建命令行接口并提供了一些很好的功能来处理选项和参数。

在命令行中,我们通过nom install commander --save来安装commander模块(添加--save选项npm会自动在packge.json中更新依赖字段)。

现在修改我们的脚本:

#!/usr/bin/env node

var program = require('commander');

program
    .version('0.0.1')
    .usage('<keywords>')
    .parse(process.argv);

if(!program.args.length) {
    program.help();
} else {
    console.log('Keywords: ' + program.args);   
}

Commander的.arg属性包含了我们传递的参数,因此我们要使用它来检查是否有参数,因为我们的命令行需要至少一个参数来进行搜索。

现在带上一个参数运行gitsearch比如gitsearch jquery将会输出Keywords: jquery。使用commander的另一个好处就是它能更具你提供的选项自动生成帮助信息,你可以尝试使用gitsearch -h

通过使用命令行提供的参数我们可以创建我们的Github搜索节点:

program
    .version('0.0.1')
    .usage('<keywords>')
    .parse(process.argv);

if(!program.args.length) {
    program.help();
} else {
    var keywords = program.args;
    var url = 'https://api.github.com/search/repositories?  sort=stars&order=desc&q='+keywords;
}

由于Github的api使用HTTP端点我们需要进行一次HTTP请求。为了使请求变得简单我们将会使用request包。

npm install request --save



#!/usr/bin/env node

var program = require('commander');
var request = require('request');

现在我们可以针对我们的特定url发送一个GET请求。

request({
    method: 'GET',
    headers: {
        'User-Agent': 'yourGithubUsername'
    },
    url: url
}, function(error, response, body) {

    if (!error && response.statusCode == 200) {
        var body = JSON.parse(body);
        console.log(body);
    } else if (error) {
        console.log('Error: ' + error);
    }
});

现在当你给gitsearch命令传递query时,它都会根据star的数量返回100个仓库。输出包含了很多数据因此我们可以使用Chalk来为输出添加样式。

npm install chalk --save


#!/usr/bin/env node

var program = require('commander');
var request = require('request');
var chalk = require('chalk');

在这里的例子中我们需要循环返回结果并挑出其中的仓库名,拥有者,描述以及clone url,同时使用chalk来添加样式。

if (!error && response.statusCode == 200) {
    var body = JSON.parse(body);

    for(var i = 0; i < body.items.length; i++) {
        console.log(chalk.cyan.bold.underline('Name: ' + body.items[i].name));
        console.log(chalk.magenta.bold('Owner: ' + body.items[i].owner.login));
        console.log(chalk.grey('Desc: ' + body.items[i].description + '\n'));
        console.log(chalk.grey('Clone url: ' + body.items[i].clone_url + '\n'));
    }
} else if (error) {
    console.log(chalk.red('Error: ' + error));
}

为了过滤我们的记过我们需要传递更多的选项和参数。Github的api有很多可选项因此我们可以通过所有者和语言来进行选择。

program
    .version('0.0.1')
    .usage('[options] <keywords>')
    .option('-o, --owner [name]', 'Filter by the repositories owner')
    .option('-l, --language [language]', 'Filter by the repositories language')
    .parse(process.argv);

if(!program.args.length) {
    program.help();
} else {
    var keywords = program.args;

    var url = 'https://api.github.com/search/repositories?sort=stars&order=desc&q='+keywords;

    if(program.owner) {
        url = url + '+user:' + program.owner;
    }

    if(program.language) {
        url = url + '+language:' + program.language;
    }

    […]
}

现在运行命令gitsearch query -o jquery -l JacaScript将会返回提到jquery,所有者为jquery,并且使用Javascript语言的仓库。

退出代码

确保你的脚本能够正常退出很重要,在这里我们再一次使用了process对象。万一有错误发生,process.exit的值必须大于0,因为成功退出的值为0.在这里我们为成功发出HTTP请求和发生错误都添加退出代码。当使用commander的.help()方法时,我们不需要错误代码,因为commander会为我们自动退出。

if (!error && response.statusCode == 200) {
    var body = JSON.parse(body);
    for(var i = 0; i < body.items.length; i++) {
        console.log(chalk.cyan.bold('Name: ' + body.items[i].name));
        console.log(chalk.magenta.bold('Owner: ' + body.items[i].owner.login));
        console.log(chalk.grey('Desc: ' + body.items[i].description + '\n'));
        console.log(chalk.grey('Clone url: ' + body.items[i].clone_url + '\n'));
}
    process.exit(0);
} else if (error) {
    console.log(chalk.red('Error: ' + error));
    process.exit(1);
}

合并功能

最后我们添加了一个--full选项来输出所有的响应数据而无需任何的操纵或者样式修改。

program
    .version('0.0.1')
    .usage('[options] <keywords>')
    .option('-o, --owner [name]', 'Filter by the repositories owner')
    .option('-l, --language [language]', 'Filter by the repositories language')
    .option('-f, --full', 'Full output without any styling')
    .parse(process.argv);



if (!error && response.statusCode == 200) {
    var body = JSON.parse(body);
    if(program.full) {
        console.log(body);
    } else {
        for(var i = 0; i < body.items.length; i++) {
            console.log(chalk.cyan.bold('Name: ' + body.items[i].name));
            console.log(chalk.magenta.bold('Owner: ' + body.items[i].owner.login));
            console.log(chalk.grey('Desc: ' + body.items[i].description + '\n'));
            console.log(chalk.grey('Clone url: ' + body.items[i].clone_url + '\n'));
        }
        process.exit(0);
    }
} else if (error) {
    console.log(chalk.red('Error: ' + error));
    process.exit(1);
}

使用其他的命令行工具例如grepless,以及pbcopy都很有用。合并功能的一个最简单的方法就是使用pipeline将上一个操作的输出变为下一个操作的输入。

pbcopy

pbcopy是从一个命令复制内容的最简单的方式。从pbcopy复制的内容允许你粘贴到其他的程序。

gitsearch jquery -f | pbcopy

less

less是一个阅读器命令,它能够将内容分割为容易操作的片段,每次只展示一屏幕的内容。

gitsearch jquery -f | less

grep

grep是一个使用正则表达式搜索文本数据的工具。

gitsearch jquery -f | grep watchers

总结

命令行工具在简化任务或者自动化执行重复的任务方面很有用,而借助Node,开发者可以轻松的创建命令行工具而无需学习编写Shell脚本。

本文中讲到的例子非常的简单,熟练掌握Node之后,你能够编写出更好的命令行工具。


本文译自Command-line utilities with Node.js,原文地址http://cruft.io/posts/node-command-line-utilities/

评论
发表评论
1年前

mark

2年前
赞了此文章!
2年前
赞了此文章!
3年前
赞了此文章!
3年前
赞了此文章!
3年前
赞了此文章!
WRITTEN BY
张小俊128
Intern in Baidu mobile search department。认真工作,努力钻研,期待未来更多可能。
TA的新浪微博
PUBLISHED IN
每天学点javascript

javascript进阶级教程,循序渐进掌握javascript

我的收藏