當Shell遇上了NodeJS

網易雲社群發表於2019-03-03

此文已由作者堯飄海授權網易雲社群釋出。

歡迎訪問網易雲社群,瞭解更多網易技術產品運營經驗。

摘要

在企業級系統維護和網際網路運維中,Shell指令碼的編寫與維護常必不可少, 但是Shell指令碼的編寫與除錯常佔用了開發人員的大部分工作時間,提高Shell指令碼的開發效率可以更好的保證專案質量。隨著NodeJs的不斷髮展,其輕量級的底層操作、跨平臺及可擴充套件性等將在系統應用開發和維護中具有更廣闊的前景。

序言

無論在傳統的企業級系統維護還是在網際網路運維中,Shell指令碼的編寫與維護常常必不可少,在系統管理員或開發人員工作中佔比重比較大的一部分。Shell指令碼的嚴格語法格式對於一般的運維人員來說,常常會在一不留神下而抓狂或查詢半天才發現是因為多了或少了一個空格或某語包括號不匹配而導致的錯誤,不但大大的浪費了指令碼維護人員的工作時間,還可能影響到工程進度甚至專案的釋出里程碑等。當然,對於非純Geek來說,最重要的還是影響心情,特別是對於一些較複雜的指令碼需求,更是必須小心謹慎,因此越來越多的開發人員必須藉助於Python、Perl、Ruby等相關的指令碼語言來實現,但是常由於平臺的特性或者語言的限制,對系統級的命令呼叫或者異常處理有限制,最終解決起來並不是十分優雅。 NodeJS的出現或許會給這些開發人員帶來一些新的選擇。

NodeJS從誕生起發展非常迅速,社群活動非常活躍,目前擴充套件模組達到1500多個,並且每天都有不同的模組提交。它是構建在JavaScript引擎V8之上的JavaScript環境,它採用基於單執行緒的非同步事件驅動I/O模型,具有非常高的效能,同時能夠支援多種平臺。日前國外的很多大的軟體或網際網路公司如Microsoft,ebay,yahoo等都在使用NodeJS,國內的網易,淘寶,新浪等網際網路企業也有很多分享和成功的線上案例應用。言歸正傳,希望下文的內容能給不熟悉或不喜歡nix平臺Shell指令碼開發或WIN平臺下的批處理編寫的工程師帶來一些幫助,為簡單起見,本文采用Nix平臺為示例,WIN平臺的使用者請參考自行修改或與作者聯絡。

首先,我承認Shell指令碼中系統命令再加上sed,awk等瑞士軍刀在一起工作已經相當強大,如果你想了解NodeJS的強大之處和如何結合Shell產生強大的工作效率,並且還能具有很好的靈活性,那就讓我們繼續旅程吧:

示例

先看一段簡單的採用Shell 指令碼執行一段命令得到其執行時間的指令碼diffa.sh:

#!/bin/bashSTART=$(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複製程式碼

執行上面的指令碼後,結果如下:

花了0 秒搞定

使用者首次執行一般會耗時幾秒,多次執行的結果可能會在0-1秒之間隨機顯示。因為du的輸出重定向,整個指令碼的執行時間非常短,並且指令碼中採用的是秒數級別的範圍,如果需要得到這個指令碼的準確執行時間只能用納秒來進行,並在Shell做除法運算,把指令碼修改一下diffb.sh。

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指令碼:

#!/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]),其他請參閱官方文件。

同樣,chmod +x 對指令碼賦予執行許可權,執行指令碼./diff.js,結果如下:

1113

上面示例中顯示的時間直接是毫秒級別,程式碼格式沒有嚴格的限制,流程的控制也會更加靈活,特別是在異常情況下,可以根據使用者的需求處理更小的細節。當然,我承認這個示例需求有些詭異,但是做這樣的比較,並不是說要二者一決高下,只是換一種前端攻城師喜歡的方法去實現一些系統運維需求。在這裡NodeJS指令碼本身也是依賴於系統Shell的強大基礎之上。

深入一點

以上示例可以看到,在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-optimist 及 isaacs`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不斷髮展和成熟,國內外廠商越來越多的成功案例與分享,在企業級和網際網路系統應用開發和維護中具有更廣闊的前景。

參考資料:

http://NodeJS.org

http://www.infoq.com/cn/articles/tyq-NodeJS-event

http://www.catonmat.net/blog/

www.cNodeJS.org

免費體驗雲安全(易盾)內容安全、驗證碼等服務

更多網易技術、產品、運營經驗分享請點選

相關文章:
【推薦】 Android輸入法彈出時覆蓋輸入框問題
【推薦】 談談驗證碼的工作原理
【推薦】 論使用者體驗測試:牛逼的功能千篇一律,好的體驗萬里挑一

相關文章