5

前语

随着node的流行,JS已经可以解决大部分问题。这对前端工程师十分友好。
相信很多同学在开发业务之余,都会写一些小脚本代替手工完成繁琐,重复的工作,从而提高工作效率。
但部分同学开发的脚本,仅局限于脚本所在路径,通过node xxx 去运行程序,这局限了脚本的使用范围和使用便捷性。

本文就给大家介绍一个简单且一劳永逸的方法,把自己开发的node脚本部署在全局环境。让脚本可以像Linux命令一样,全局便捷地使用,从此打开新世界的大门。

文件夹

全局脚本设置的本质思路

其实原理很简单:将Linux的全局命令搜索路径,加上脚本所在文件夹的路径。

文件夹

具体设置过程

找到终端配置文件
  • 终端配置文件默认路径为「/User/用户名」,笔者的mac用户名为Momo,故以下示例中用户名均为「Momo」。
  • 原配终端为bash,对应配置文件为「.bash.rc」。装了zsh终端的同学,对应修改「.zsh.rc」。
  • 解析:.rc文件为终端的配置文件,在重启终端,或者新开终端tab都会读取该文件。

文件夹

修改Linux的全局命令搜索路径
  • 打开文件,加上脚本所在文件夹。笔者的脚本均放在了myShell(其实应该叫myScript)文件夹中,所以加上一句export PATH=/Users/Momo/myShell:$PATH
  • 解析:在Linux中,全局命令搜索路径就是通过PATH变量保存起来的。「:」是字符串链接符的意思。类似于js中,var str = '1' + '2';中的「+」
    文件夹
修改js脚本文件
  • 在头部加上#!/usr/bin/env node'use strict';
  • 解析:#!/usr/bin/env node是指本脚本是通过「/usr/bin/env」路径下的node软件运行。相当于文件中什么软件打开。'use strict';是指使用js的严格语法。
  • 注意这两句一定要放置为js文件顶部,否则系统将不知道用什么软件执行。运行失败
    文件夹
修改js脚本文件权限
  • 终端运行chmod 777 脚本文件名,如示例chmod 777 mop
  • 解析:chmod是linux下修改文件权限的工具,777代表所有读写权限。具体的百度chmod即可。权限设置了以后,脚本的图标将变成下面这个样子。

    文件夹

重启或新建终端,执行脚本。
  • 解析:重启或新建终端是为了读取到刚修改的终端配置文件,让Linux的全局命令搜索路径生效。遍历到我们所开发的脚本。

脚本常用功能

设置不同颜色的console.log

  • 介绍:不同颜色的log除了美观,还可以起到警示的作用。
  • 基本用法:

    // colors不是Node自带模块,需要事先npm install colors安装
    const colors = require('colors'); // 引用colors模块,常用颜色    black,red,green,yellow,blue,magenta,cyan,white,gray,grey
    console.log(colors.red('filePath or targetPath can not be empty!')); // 在控制台输出红色的文案

获取终端所在目录路径

  • 介绍:如题所示,获取终端当前所在目录,而不是脚本所在路径
  • 基本用法:let basePath = process.cwd(); // 其中process是node全局变量,提供当前 Node 进程的信息

携带参数运行脚本

  • 介绍:平时我们使用linux命令都会伴随一些参数,那么在node中怎么实现,怎么获取运行时携带的参数呢?通过process.argv即可,它将返回一个数组,由命令行执行脚本时的各个参数组成。它的第一个成员总是node,第二个成员是脚本文件名,其余成员是脚本文件的参数。
  • 基本用法:

    let elem1 = process.argv[2]; // 携带的参数一
    let elem2 = process.argv[3]; // 携带的参数二

调用linux命令

  • 介绍:有时候我们需要的功能并不是仅靠node就能实现的,还需要linux命令做支持。那怎么通过node调用Linux命令呢?
  • 基本用法:

    const child_process = require('child_process'); // child_process是node负责子进程的模块
    child_process.exec('ls -a', function (error, stdout, stderr) { // 通过child_process下的exec函数执行linux命令
        error && console.log('error  ' + error); // 执行出错时的错误信息
        console.log('Child Process STDOUT: ' + stdout); // stdout是执行linux命令后的执行   结果。在这里即返回执行ls -a命令遍历到的文件信息   
    });

打开页面

  • 介绍:怎么通过Node用浏览器打开特定页面呢?mac自带了open命令,我们通过node调用open命令打开页面即可。
  • 基本用法:

    require('child_process').exec(`open http://baidu.com`); // 打开百度

    // 这是网上找到的,兼容各运行环境的打开页面方法
    let cmd = ''; // 运行的指令
    if (process.platform == 'wind32') {
      cmd = 'start "%ProgramFiles%\Internet Explorer\iexplore.exe"';
    } else if (process.platform == 'linux') {
      cmd = 'xdg-open';
    } else if (process.platform == 'darwin') {
      cmd = 'open';
    }
    require('child_process').exec(`${cmd} http://baidu.com`);

携带上下文执行linux命令

  • 介绍:上面介绍到的调用linux方法,本质上只是直接去调用具体的Linux命令。如调用ls,相当于直接找到系统里面ls这个脚本,执行它。
    这和我们平常在终端里面执行有什么区别呢?
    在终端里面,我们调用命令是携带上下文的。即第二条命令会在执行完第一条命令之后的环境下执行,例如

    cd /
    ls

    这两条命令是先切换到根路径,再打印跟路径下的文件信息。
    如果像上面一样,通过

    require('child_process').exec(`cd /`);
    require('child_process').exec(`ls`);

    则只是执行了两个相互独立的命令,一个是切换目录,一个是打印文件信息。ls打印的不是切换目录后的文件信息,而是运行脚本时所在的文件信息。
    那怎么携带上下文执行linux命令呢?

  • 基本用法:

    // 注意,这里使用到了colors模块,用于显示不同颜色的输出。不需要的话,也可以直接console.log()打印。
    const subProcess = require('child_process').spawn("bash"); // 使用子程序去运行某个软件,在这里就是运行bash软件。相当于运行一个终端
    subProcess.stdout.on('data', function(data) { console.log(colors.green(data)); }); // 监听命令执行后,bash返回的信息
    subProcess.on('error', function() { console.log(colors.red('error\n' + arguments)); }); // 消息的错误监听
    subProcess.on('close', (code) => { // 关闭bash进程后触发的事件
    if (code === 0) {
        console.log(colors.blue(`执行成功!`));
    } else {
        console.log(colors.blue(`执行失败,错误码为:${code}`));
    }
    }); // 监听进程退出
    //向子进程发送命令
    subProcess.stdin.write(`cd / \n`);   // 切换目录,\n表示回车,执行cd命令
    subProcess.stdin.write(`ls -a \n`);   // 写入数据
    subProcess.stdin.end(); // 结束进程,相当于关闭终端

将某数据复制到剪切板

  • 介绍:这是一个很常用的功能
  • 基本用法:

    const copyProcess = require('child_process').spawn("bash"); // 用于复制密码的进程
    copyProcess.stdin.write(`echo ${Config.server.password} | pbcopy`); // 将特定文本拷贝到剪切板中
    copyProcess.stdin.end(); // 结束进程

    文件夹

常用脚本

上面介绍了一些基本的node功能,虽然看似很简答。但如果善于运用,也可以做出一些提高效率的小工具。

mhelp,一键查看自定义文档

  • 功能介绍:自定义常用文档,方便地查看。例如markdown语法,常用全局匹配的正则什么的。省的重复打开笔记。
  • 基本思路:「colors颜色控制」+「携带参数运行脚本」
  • 示例代码:
#!/usr/bin/env node
'use strict';
const colors = require('colors'); // 命令行颜色black,red,green,yellow,blue,magenta,cyan,white,gray,grey
let helpName = process.argv[2]; // 需要查看的文档名
let helpInfo = {
    markdown: {
        '无需列表': '.1 xxx  .1 xxx    .1 xxx',
        '有需列表': '- xxx   - xxx   - xxx',
    }
}; // 自定义帮助文档
// 设置文档name为他本身
let allHelpName = '';
let match = false; // 是否找到匹配项
for (let helpItem in helpInfo) {
    allHelpName += helpItem + `\n`;
    if (helpItem === helpName) {
        match = true;
        for (let detailItem in helpInfo[helpItem]) {
            console.log(colors.green(detailItem + ' : ' + helpInfo[helpItem][detailItem])); // 找不到页面相关信息
        }
        return;
    }
}
if (!match) {
    console.log(colors.red('can not find matched helpInfo!    the all helpName is')); // 找不到页面相关信息
    console.log(colors.red(allHelpName)); // 找不到页面相关信息
}

mop,一键打开常用页面并复制密码到剪切板

  • 功能介绍:简单,一个命令打开特定页面,而且帮你把特定文案复制到剪切板。例如?登陆密码。
  • 基本思路:「复制剪切板」+「打开页面」+「colors颜色控制」
  • 示例代码:
#!/usr/bin/env node
'use strict';
const proess = require('child_process');
const copyProcess = proess.spawn("bash"); // 用于复制密码的进程,为了避免同一个进程拷贝和上传时的冲突
const colors = require('colors'); // 命令行颜色black,red,green,yellow,blue,magenta,cyan,white,gray,grey
const dataInfo = require('../config').dataInfo;
const introduce = require('../config').shellInfo['mop'].introduce; // 脚本介绍,用在momoShell中介绍
let dataName = process.argv[2]; // 需要打开的页面
let onlyShow = process.argv[3]; // 是否只显示数据,不打开页面
let dataItem = dataInfo[dataName]; // 遍历成功后获取的页面对象
// 输入脚本简介
if (process.argv[2] === '-h') {
    console.log(colors.green(introduce));
    copyProcess.stdin.end();
    return;
}
// 检测数据有效性
if (!dataName) { // 参数为空
    console.log(colors.red('dataName can not be empty!'));
    copyProcess.stdin.end();
    return;
} else if (!dataItem) { // 找不到页面信息
    let allDataName = '';
    for (let dataItem in dataInfo) {
        allDataName += `${dataItem}【${dataInfo[dataItem].info}】\n`;
    }
    console.log(colors.red('can not find matched dataInfo!    the all dataName is')); // 找不到页面相关信息
    console.log(colors.red(allDataName)); // 找不到页面相关信息
    copyProcess.stdin.end();
    return;
}
console.log(colors.green(`【name】${dataItem.name}`));
dataItem.account && console.log(colors.green(`【account】${dataItem.account}`));
dataItem.password && console.log(colors.green(`【password】${dataItem.password}`));
dataItem.url && console.log(colors.green(`【url】${dataItem.url}`));
// 将密码拷贝到剪切板中
copyProcess.stdin.write(`echo ${dataItem.password} | pbcopy`);   // 写入数据
copyProcess.stdin.end();
!onlyShow && dataItem.url && require('child_process').exec(`open ${dataItem.url}`); // 打开特定页面

mgulp,一键gulp打包项目,一键动态刷新

  • 功能介绍:解决了每个项目都需要配置gulp及其依赖的麻烦。功能就如标题所说,一键gulp打包项目,一键动态刷新(需结合livereload工具
  • 基本思路:「携带参数运行脚本」+「携带上下文执行linux命令」
  • 示例代码:(这里还需结合gulpfile.js文件一起使用,详情请查看笔者github上的文件结构)
#!/usr/bin/env node
'use strict';
const Process = require('child_process').spawn("bash"); // 使用子程序去运行某个软件。在这里就是运行bash软件。并获取其上下文。
const colors = require('colors'); // 命令行颜色black,red,green,yellow,blue,magenta,cyan,white,gray,grey
const Config = require('../config'); // 服务器信息
const introduce = Config.shellInfo['mgulp'].introduce; // 脚本介绍,用在momoShell中介绍
const elem = process.argv; // 输入的参数
const basePath = process.cwd();
const action = elem[2]; // 文件名
// 消息监听,监听子进程的输出。并在主进程中打印出来。
function onData(data) { console.log(colors.green(data)); }
// 设置消息监听
Process.stdout.on('data', onData);
Process.on('error', function() { console.log(colors.red('error\n' + arguments)); });
Process.on('close', (code) => {
    if (code === 0) {
        console.log(colors.blue(`执行成功!`));
    } else {
        console.log(colors.blue(`执行成功失败,错误码为:${code}`));
    }
}); // 监听进程退出
if (action === '-h') { // 输入脚本简介
    console.log(colors.green(introduce));
    return;
} else if (action === '-publish') { // 输入脚本简介
    let inputPath = basePath + '/' + (elem[3] || ''); // 文件输入
    let outputPath = basePath + '/' + elem[4]; // 文件输出
    if (!elem[4]) {
        outputPath = basePath + `/a-gulp-publish/${elem[3]}`;
    }
    Process.stdin.write(`cd /Users/Momo/Desktop/intruction/Node/shell \n`); // 切换pwd
    Process.stdin.write(`gulp default --${inputPath} --${outputPath} \n`); // 执行gulp,通过「--」来让gulp不解析为gulp任务
    Process.stdin.end();
} else if (action === '-watch') { // 输入脚本简介
    let watchList = elem[3];
    if (!watchList) {    // 检测数据有效性
        console.log(colors.red('watchList can not be empty!'));
    } else {
        watchList = watchList.split(',').map((item) => { // 格式化路径
            item = basePath + '/' + item;
            item.replace(/\/\//g, '/'); // 去除双斜杠
            if (item.indexOf('*') === -1) { // 监听所有文件,及旗下文件夹内的文件
                item = item + '/*.*,' + item + '/*/*.*';
            }
            return item;
        });
        Process.stdin.write('cd /Users/Momo/Desktop/intruction/Node/shell \n'); // 切换pwd
        Process.stdin.write('gulp reload --${watchList.join(',')} \n'); // 执行gulp
        Process.stdin.end();
    }
} else { // 输入脚本简介
    console.log(colors.red('please input action'));
    Process.stdin.end();
}

mupload,一键上传文件或文件夹到服务器

  • 功能介绍:如题,可上传整个文件夹,不需要打开ftp软件这么麻烦(注意scp命令不支持强制覆盖文件夹功能)
  • 基本思路:「复制剪切板」+「调用linux命令」+ 「scp命令」
  • 示例代码:
#!/usr/bin/env node
'use strict';
const colors = require('colors'); // 命令行颜色black,red,green,yellow,blue,magenta,cyan,white,gray,grey
const Config = require('../config'); // 服务器信息
const copyProcess = require('child_process').spawn("bash"); // 用于复制密码的进程,为了避免同一个进程拷贝和上传时的冲突
const subProcess = require('child_process').spawn("bash"); // 使用子程序去运行某个软件。在这里就是运行bash软件。并获取其上下文。
const introduce = Config.shellInfo['mupload'].introduce; // 脚本介绍,用在momoShell中介绍
let elem = process.argv; // 输入的参数
let basePath = process.cwd();
let filePath = basePath + '/' + elem[2]; // 文件名
let targetPath = elem[3]; // 目标路径
// 将服务器密码拷贝到剪切板中
copyProcess.stdin.write(`echo ${Config.server.password} | pbcopy`);   // 写入数据
copyProcess.stdin.end();
// 输入脚本简介
if (process.argv[2] === '-h') {
    console.log(colors.green(introduce));
    copyProcess.stdin.end();
    subProcess.stdin.end();
    return;
}
// 检测数据有效性
if (!filePath || !targetPath) {
    console.log(colors.red('filePath or targetPath can not be empty!'));
    subProcess.stdin.end();
    return;
}
// 兼容目标路径
if (targetPath[targetPath.length - 1] === '/') {
    if (elem[2].indexOf('/') !== -1) {
        targetPath += elem[2].substr(elem[2].indexOf('/') + 1);
    } else {
        targetPath += elem[2];
    }
}
// 消息监听,监听子进程的输出。并在主进程中打印出来。
function onData(data) { console.log(colors.green(data)); }
//设置消息监听
subProcess.stdout.on('data', onData);
subProcess.on('error', function() { console.log(colors.red('error\n' + arguments)); });
subProcess.on('close', (code) => {
    if (code === 0) {
        console.log(colors.blue(`上传成功!`));
        console.log(colors.red(`注意,上传文件夹并不会覆盖原文件,请登录服务器查看文件是否替换成功`));
    } else {
        console.log(colors.blue(`上传失败,错误码为:${code}`));
    }
}); // 监听进程退出
//向子进程发送命令
subProcess.stdin.write(`scp -C -r -p ${filePath} root@${Config.server.ip}:${targetPath} \n`);   // 写入数据
subProcess.stdin.end();
还有其他的,像什么「一键git」「一键svn」,这些就根据实际需要组合开发啦。

问题求助

笔者在开发脚本时,遇到两个问题,有兴趣的大神可以指点指点

文件夹

复制剪切板和scp的冲突

这是「一键上传文件或文件夹到服务器」遇到的问题,复制剪切板和scp命令会冲突,如果放在同一个bash进程执行会失败,没找到原因。

linux应答式异步交互,实现ssh的一键登录。

这个功能我用linux的shell脚本实现过。但是放在node没能找到实现思路,


momo707577045
2.4k 声望603 粉丝

[链接]