當Shell趕上了NodeJS

序言

不管在傳統的企業級系統維護仍是在互聯網運維中,Shell腳本的編寫與維護經常必不可少,在系統管理員或開發人員工做中佔比重比較大的一部分。 Shell腳本的嚴格語法格式對於通常的運維人員來講,經常會在一不留神下而抓狂或查找半天才發現是由於多了或少了一個空格或某語包括號不匹配而致使的錯 誤,不但大大的浪費了腳本維護人員的工做時間,還可能影響到工程進度甚至項目的發佈里程碑等。固然,對於非純Geek來講,最重要的仍是影響心情,特別是 對於一些較複雜的腳本需求,更是必須當心謹慎,所以愈來愈多的開發人員必須藉助於Python、Perl、Ruby等相關的腳本語言來實現,可是常因爲平 臺的特性或者語言的限制,對系統級的命令調用或者異常處理有限制,最終解決起來並非十分優雅。 NodeJS的出現或許會給這些開發人員帶來一些新的選擇。 html

NodeJS從誕生起發展很是迅速,社區活動很是活躍,目前擴展模塊達到1500多個,而且天天都有不一樣的模塊提交。它是構建在JavaScript引擎V8之上的JavaScript環境,它採用基於單線程的異步事件驅動I/O模型,具備很是高的性能, 同時可以支持多種平臺。日前國外的不少大的軟件或互聯網公司如Microsoft,ebay,yahoo等都在使用NodeJS,國內的網易,淘寶,新浪 等互聯網企業也有不少分享和成功的線上案例應用。言歸正傳,但願下文的內容能給不熟悉或不喜歡nix平臺Shell腳本開發或WIN平臺下的批處理編寫的 工程師帶來一些幫助,爲簡單起見,本文采用Nix平臺爲示例,WIN平臺的用戶請參考自行修改或與做者聯繫。 前端

首先,我認可Shell腳本中系統命令再加上sed,awk等瑞士軍刀在一塊兒工做已經至關強大,若是你想了解NodeJS的強大之處和如何結合Shell產生強大的工做效率,而且還能具備很好的靈活性,那就讓咱們繼續旅程吧: node

示例

先看一段簡單的採用Shell 腳本執行一段命令獲得其執行時間的腳本diffa.sh: git

#!/bin/bash
START=$(date +%s)
# prepare things
du -m /home > /tmp/output
# done  END=$(date +%s)
DIFF=$(( $END - $START ))
echo "化了$DIFF 秒搞定"
chmod +x diff.sh
sh diff.sh

執行上面的腳本後,結果以下: github

化了0 秒搞定

用戶首次執行通常會耗時幾秒,屢次執行的結果可能會在0-1秒之間隨機顯示。由於du的輸出重定向,整個腳本的執行時間很是短,而且腳本中採用的是 秒數級別的範圍,若是須要獲得這個腳本的準確執行時間只能用納秒來進行,並在Shell作除法運算,把腳本修改一下diffb.sh。 chrome

START=$(date +%s%N)
du -m /home/ >/tmp/output
END=$(date +%s%N)
DIFF=$(($END - $START))
SUM=`expr $DIFF / 1000000`
echo "化了$SUM MS搞定"

執行一下上面的diffb 腳本就能夠獲得運行的結果了,須要提醒的是上面的腳本中各表達式的格式都是即定的,若是開發人員不當心多了一個空格或少了一空格,都將致使腳本錯誤。下面 採用NodeJS來試試看,首上下載與安裝NodeJS環境,過程很是簡單,具體請參考官方網站或直接apt-get 之類的操做。編寫以下diffc.js腳本: apache

#!/usr/bin/env node

var util = require('util'),
    spawn = require('child_process').spawn,
    ls = spawn('du', ['-m', '/home/']);
var start = +new Date();
ls.stdout.on('data', function (data) {
    //console.log('stdout: ' + data);
});
ls.stderr.on('data', function (data) {
    console.log('stderr: ' + data);
});
ls.on('exit', function (code) {
    var end = +new Date();
    console.log(end - start);
});

注:上面require中引用的都是系統內置模塊,spawn的格式爲spawn(command, [args], [options]),其餘請參閱官方文檔。 npm

一樣,chmod +x 對腳本賦予執行權限,執行腳本./diff.js,結果以下: 編程

1113

上面示例中顯示的時間直接是毫秒級別,代碼格式沒有嚴格的限制,流程的控制也會更加靈活,特別是在異常狀況下,能夠根據用戶的需求處理更小的細節。 固然,我認可這個示例需求有些詭異,可是作這樣的比較,並非說要兩者一決高下,只是換一種前端攻城師喜歡的方法去實現一些系統運維需求。在這裏 NodeJS腳本自己也是依賴於系統Shell的強大基礎之上。 api

深刻一點

以上示例能夠看到,在Shell環境中,NodeJS內置模塊實現經常使用的功能是即方便又靈活,Linux Shell環境中比較強大的功能之一就是支持輸入輸出重定向功能,用符號<和>來表示。0、1和2分別表示標準輸入、標準輸出和標準錯誤信息 輸出,用來指定須要重定向的標準輸入或輸出,好比 2>error.log表示將錯誤信息輸出到文件err.log中。相似的,NodeJS中能夠直接採用超複雜的命令來搞定,通常對於咱們這些非系 統管理員有必定的難度,下面引入強大點的模塊procstreams,它能夠實現輸出流重定向等功能,首先用戶須要執行npm install procstreams安裝模塊,編寫示例以下wc.js:

#!/usr/bin/env node

var p = require('procstreams');
p('cat app.log').pipe('wc -l').data(function (stdout, stderr) {
    console.log(stdout);
});

wc.js腳本代碼是藉助於Shell命令實現統計app.log的行數,至關於Shell環境中的cat app.log | wc -l功能,輸出的結果能夠再根據須要再進行靈活處理,另外它還支持then、and和or等操做,相似Shell腳本中的;、&& 和||操做。在實現複雜或交互的功能時,甚至能夠徹底採用交互的方式進行操做輸入。

另外,用戶執行腳本的時候還須要處理複雜一些的參數對應,node-optimistisaacs's nopt 之類的模塊能夠很是簡單的幫助攻城師實現這樣的功能,如實現根據用戶的輸入的參數執行須要的系統命令,並能夠作相關的邏輯處理的opt.js:

#!/usr/bin/env node

var util = require('util'),
    spawn = require('child_process').spawn;
var argv = require('optimist').argv;
var cmd = argv.cmd;
var args = argv.args
var option = argv.opt
var ls = spawn(cmd, [args, option]);
ls.stdout.on('data', function (data) {
    if (!data || !! data) console.log(' i believe it');
});
ls.stderr.on('data', function (data) {
    console.log('It\'s a miracle!');
});
ls.on('exit', function (code) {
    console.log('it.justHappened();');
});

用戶使用以下對應格式執行代碼:./opt.js --cmd=ls --args=/m --opt=/home,而後只須要在代碼相關處添加對應的邏輯代碼,把注意力放在業務層,採用js的流程控制實現業務邏輯的分離。

實際應用

在企業線上或系統運維中,常須要對一些進程進行監控和報警,以便通知相關係統管理人員,以下Shell腳本 agenta.sh實現了對tomcat6進程監控,若是不存在自動重啓。

#!/bin/sh
pid=`ps aux| grep "tomcat6" | grep -v grep | sed -n  '1P' | awk '{print $2}'`
if [ -z $pid ]; then
        echo "begin restart,please waiting..."
        sudo /etc/init.d/tomcat6 restart
        exit 1
else
        echo -e "exist ,don't need restart"
fi

腳本編寫人員在通過一番努力與折騰後,完成了代碼編寫與調試工做,而後須要經過系統的crontab功能添加如0-59/2 * * * * sh agent.sh的定時任務,若是系統管理員把crontab的權限給禁用了,那就須要獲得系統管理員的幫助了。下面使用Nodejs來實現一樣的功能, 先假設讀者對grep、sed和awk等經常使用命令的使用有大概瞭解,代碼以下agentb.js:

var p = require('procstreams');
var exec = require('child_process').exec;
setInterval(function () {
    exec("ps aux| grep 'tomcat6' | grep -v grep | sed -n  '1P' | awk '{print $2}'", function (err, output) {
        if (err) throw err;
        if (output.length > 0) console.log('exist,don\'t need restart');
        else exec('sudo /etc/init.d/tomcat6 restart', function (err2, out2) {});
    })
}, 1000 * 60 * 2);

示例代碼中setInterval的函數的做用經過設置一個回調函數和間隔執行時間來實現定時監控。運行代碼後,一樣能夠實現進程監控的功能,也許 你會說上面的Shell命令仍是不少的。由於你以爲直接使用Shell腳本會更簡單,但是若是你經歷過爲空格或配置之類的調試過程,或者需求更加複雜時, 採用NodeJS會讓你以爲很是輕鬆。更重要的是,編寫腳本後,在執行腳本時你能夠直接經過chrome debug 工具設置斷點與單步調試,或者在chrome 瀏覽器上進行圖形化調試等操做,具體請參考node-inspector。如今,agentb.js代碼中的Shell命令仍是太長了太複雜,調試起來也 不太方便,使用procstreams作一下簡化,實現代碼agentc.js以下:

var p = require('procstreams');
setInterval(function () {
    p("ps aux").pipe('grep tomcat6').pipe('grep -v grep').pipe('sed -n 1P').pipe("awk $2")
	   .data(function (stdout, stderr) {
        if (stderr) throw stderr;
        if (stdout.length > 0) {
            console.log('exist,don\'t need restart');
        } else {
            console.log('restart,waiting...');
            p('sudo /etc/init.d/tomcat6 restart', function (stdout, stderror) {
                console.log(stdout);
            });
        }
    });
}, 1000 * 60 * 2);

agentc代碼中經過pipe操做能夠實現對每一個步驟的輸入進行詳細的跟蹤與調試,可是腳本中仍是須要對系統的不少內置命令有大概的瞭解,須要對 操做系統的相關功能或語法格式比較熟悉,使用起來仍是有點不習慣。攻城師都喜歡編程時能控制住本身把握的,或者在使用簡單的命令的狀況下,就能實現須要的 功能,再次簡化代碼後獲得agentd.js

var p = require('procstreams');
var serviceName = 'tomcat6';
var interval = 1000 * 60 * 2;
setInterval(function () {
    p("ps aux").pipe('grep ' + serviceName).data(function (stdout, stderr) {
        if ( !! stdout && stdout.indexOf(serviceName) == 0) {
            console.log('exist,don\'t need restart');
        } else {
            console.log('restart,waiting...');
            p('sudo /etc/init.d/tomcat6 restart', function (stdout, stderror) {});
        }
    });
}, interval);

在通過此次修改以後,對系統命令的掌握程度要求明顯更低了,題外話,用戶對系統命令瞭解的越詳細越好,但若是使用簡單即美的指導去實現一樣的需求,何樂而不爲。代碼中serviceName 和interval 參數能夠經過node-optimist模塊動態給定,這樣就能夠實現一份代碼監控多個進程,而且不須要系統管理員的幫助去添加定時任務的操做。固然,但願這樣操做不會影響系統功能或在權限範圍內。

總結

儘管Linux的Shell環境編程很是的強大,可是編寫或調試Shell腳本時常使人抓狂不已,也沒有很好的圖形化調試工具。固然腳本較複雜時, 尤爲在需求跨平臺時,腳本改動比常較大,日前,開發人員須要根據平臺的不一樣,準備多套腳本代碼,如tomcat,apache等,若是採用簡單Shell 和NodeJS結合編程,或許只須要把平臺相關的命令提取出來,只需較少改動就能實現跨平臺,能夠大大提升工做效率與減小浪費攻城師的時間。我的認爲,採 用兩者結合的方式具備如下優勢:

  1. 採用v8引擎,輕量級模塊,較好跨平臺性,較底層的系統操做,在系統監控運維等方面具備明顯優點,
  2. 採用事件驅動非阻塞IO模型,無線程上下文切換和鎖操做,, 可利用多核CPU計算,性能較高,
  3. 開放源代碼,社區活躍,模塊豐富,底層的擴展實現也較方便。

隨着NodeJS不斷髮展和成熟,國內外廠商愈來愈多的成功案例與分享,在企業級和互聯網系統應用開發和維護中具備更廣闊的前景。

相關文章
相關標籤/搜索