1. 程式人生 > 程式設計 >nodejs編寫bash指令碼的終極方案分享

nodejs編寫bash指令碼的終極方案分享

目錄
  • 前言
  • zx庫
    • $`command`
    • cd()
    • fetch()
    • question()
    • sleep()
    • nothrow()
    • chalk
    • fs
    • os
    • $.shell
    • $.quote
    • 傳遞環境變數
    • 傳遞陣列
  • 總結

    前言

    最近在學習bash語法,但是如果對bash語法不是熟手的話,感覺非常容易出錯,比如說:顯示未定義的變數shell中變數沒有定義,仍然是可以使用的,但是它的結果可能不是你所預期的。舉個例子:

    #!/bin/bash
    
    # 這裡是判斷變數var是否等於字串abc,但是var這個變數並沒有宣告
    if [ "$var" = "abc" ] 
    then
       # 如果if判斷裡是true就在控制檯列印 “ not abc”
       echo  " not abc" 
    else
       # 如果if判斷裡是false就在控制檯列印 “ abc”
       echo " abc "
    fi
    

    結果是列印了abc,但問題是,這個指令碼應該報錯啊,變數並沒有賦值算是錯誤吧。

    為了彌補這些錯誤,我們學會在指令碼開頭加入:set -u

    這句命令的意思是指令碼在頭部加上它,遇到不存在的變數就會報錯,並停止執行。

    再次執行就會提示:test.sh: 3: test.sh: num: parameter not set

    再想象一下,你本來想刪除:rm -rf $dir/*然後dir是空的時候,變成了什麼?rm -rf是刪除命令,$dir是空的話,相當於執行 rm -rf /*,這是刪除所有檔案和資料夾。。。然後,你的系統就沒了,這就是傳說中的刪庫跑路嗎~~~~

    如果是node或者瀏覽器環境,我們直接var === 'abc' 肯定是會報錯的,也就是說很多經驗無法複用到bash來,如果能複用的話,該多好啊。

    後來就開始探索,如果用node指令碼代替bash該多好啊,經過一天折騰逐漸發現一個神器,Google旗下的zx庫,先彆著急,我先不介紹這個庫,我們先看看目前主流用node如何編寫bash指令碼,就知道為啥它是神器了。

    node執行bash指令碼: 勉強解決方案:child_process API

    例如 child_process的API裡面exec命令

    const { exec } = require("child_process");
    
    exec("ls -la",(error,stdout,stderr) => {
        if (error) {
            console.log(`error: ${error.message}`);
            return;
        }
        if (stderr) {
            console.log(`stderr: ${stderr}`);
            return;
        }
        console.log(`stdout: ${stdout}`);
    });
    

    這裡需要注意的是,首先exec是非同步的,但是我們bash指令碼命令很多都是同步的。

    而且注意:error物件不同於stderr. error當child_process模組無法執行命令時,該物件不為空。例如,查詢一個檔案找不到該檔案,則error物件不為空。但是,如果命令成功執行並將訊息寫入標準錯誤流,則該stderr物件不會為空。

    當然我們可以使用同步的exec命令,execSync

    // 引入 exec 命令 from child_process 模組
    const { execSync } = require("child_process");
    
    // 同步建立了一個hello的資料夾
    execSync("mkdir hello");
    

    再簡單介紹一下child_process的其它能夠執行bash命令的api

    • spawn: 啟動一個子程序來執行命令
    • exec:啟動一個子程序來執行命令,與spawn不同的是,它有一個回撥函式能知道子程序的情況
    • execFile:啟動一子程序來執行可執行檔案
    • fork:與spawn類似,不同點是它需要指定子程序需要需執行的script檔案

    exec跟ececFile不同的是,exec適合執行命令,eexecFile適合執行檔案。

    node執行bash指令碼: 進階方案 shell

    const shell = require('shelljs');
     
    # 刪除檔案命令
    shell.rm('-rf','out/Release');
    // 拷貝檔案命令
    shell.cp('-R','stuff/','out/Release');
     
    # 切換到lib目錄,並且列出目錄下到.js結尾到檔案,並替換檔案內容(sed -i 是替換文字命令)
    shell.cd('lib');
    shell.ls('*.js').forEach(function (file) {
      shell.sed('-i','BUILD_VERSION','v0.1.2',file);
      shell.sed('-i',/^.*REMOVE_THIS_LINE.*$/,'',/.*REPLACE_LINE_WITH_MACRO.*\n/,shell.cat('macro.js'),file);
    });
    shell.cd('..');
     
    # 除非另有說明,否則同步執行給定的命令。 在同步模式下,這將返回一個 ShellString
    #(與 ShellJS v0.6.x 相容,它返回一個形式為 { code:...,stdout:...,stderr:... } 的物件)。
    # 否則,這將返回子程序物件,並且回撥接收引數(程式碼、標準輸出、標準錯誤)。
    if (shell.exec('git commit -am "Auto-commit"').code !== 0) {
      shell.echo('Error: Git commit failed');
      shell.exit(1);
    }
    

    從上面程式碼上看來,shelljs真的已經算是非常棒的nodejs寫bash指令碼的方案了,如果你們那邊的node環境不能隨便升級,我覺得shelljs確實夠用了。

    接著我們看看今天的主角zx,start已經17.4k了。

    zx庫

    官方網址:www.npmjs.com/package/zx

    我們先看看怎麼用

    #!/usr/bin/env zx
    
    await $`cat package.json | grep name`
    
    let branch = await $`git branch --show-current`
    await $`dep deploy --branch=${branch}`
    
    await Promise.all([
      $`sleep 1; echo 1`,$`sleep 2; echo 2`,$`sleep 3; echo 3`,])
    
    let name = 'foo bar'
    await $`mkdir /tmp/${name}
    

    各位看官覺得咋樣,是不是就是在寫命令而已,bash語法可以忽略很多,直接上js就行,而且它的優點還不止這些,有一些特點挺有意思的:

    1、支援ts,自動編譯.ts為.mjs檔案,.mjs檔案是node高版本自帶的支援es6 module的檔案結尾,也就是這個檔案直接import模組就行,不用其它工具轉義

    2、自帶支援管道操作pipe方法

    3、自帶fetch庫,可以進行網路請求,自帶chalk庫,可以列印有顏色的字型,自帶錯誤處理nothrow方法,如果bash命令出錯,可以包裹在這個方法裡忽略錯誤

    完整中文文件(在下翻譯水平一般,請見諒)

    #!/usr/bin/env zx
    
    await $`cat package.json | grep name`
    
    let branch = await $`git branch --show-current`
    await $`dep deploy --branch=${branch}`
    
    await Promise.all([
      $`sleep 1; echo 1`,])
    
    let name = 'foo bar'
    await $`mkdir /tmp/${name}
    

    Bash 很棒,但是在編寫指令碼時,人們通常會選擇更方便的程式語言。 JavaScript 是一個完美的選擇,但標準的 Node.js 庫在使用之前程式設計客棧需要額外的做一些事情。 zx 基於 child_process ,轉義引數並提供合理的預設值。

    安裝

    npm i -g zx
    

    需要的環境

    Node.js程式設計客棧 >= 14.8.0
    

    將指令碼寫入副檔名為 .mjs 的檔案中,以便能夠在頂層使用await。

    將以下 shebang新增到 zx 指令碼的開頭:

    #!/usr/bin/env zx
    現在您將能夠像這樣執行您的指令碼:
    
    chmod +x ./script.mjs
    ./script.mjs
    

    或者通過 zx可執行檔案:

    zx ./script.mjs
    

    所有函式($、cd、fetch 等)都可以直接使用,無需任何匯入。

    $`command`

    使用 child_process 包中的 spawn 函式執行給定的字串,並返回 ProcessPromise.

    let count = parseInt(await $`ls -1 | wc -l`)
    console.log(`Files count: ${count}`)
    

    例如,要並行上傳檔案:

    如果執行的程式返回非零退出程式碼,ProcessOutput 將被丟擲

    try {
      await $`exit 1`
    } catch (p) {
      console.log(`Exit code: ${p.exitCode}`)
      console.log(`Error: ${p.stderr}`)
    }
    

    ProcessPromise,以下是promise typescript的介面定義

    class ProcessPromise<T> extends Promise<T> {
      readonly stdin: Writable
      readonly stdout: Readable
      readonly stderr: Readable
      readonly IbuaBpexitCode: Promise<number>www.cppcns.com
      pipe(dest): ProcessPromise<T>
    }
    

    pipe() 方法可用於重定向標準輸出:

    await $`cat file.txt`.pipe(process.stdout)
    

    閱讀更多的關於管道的資訊 .com/google/zx/b…

    ProcessOutput的typescript介面定義

    class ProcessOutput {
      readonly stdout: string
      readonly stderr: string
      readonly exitCode: number
      toString(): string
    }
    

    函式:

    cd()

    更改當前工作目錄

    cd('/tmp')
    await $`pwd` // outputs /tmp
    

    fetch()

    node-fetch 包。

    let resp = await fetch('http://wttr.in')
    if (resp.ok) {
      console.log(await resp.text())
    }
    

    question()

    readline包

    let bear = await question('What kind of bear is best? ')
    let token = await question('Choose env variable: ',{
      choices: Object.keys(process.env)
    })
    

    在第二個引數中,可以指定選項卡自動完成的選項陣列

    以下是介面定義

    function question(query?: string,options?: QuestionOptions): Promise<string>
    type QuestionOptions = { choices: string[] }
    

    sleep()

    基於setTimeout 函式

    await sleep(1000)
    

    nothrow()

    將 $ 的行為更改,如果退出碼不是0,不跑出異常.

    ts介面定義

    function nothrow<P>(p: P): P
    

    await nothrow($`grep something from-file`)
    // 在管道內:
    
    await $`find ./examples -type f -print0`
      .pipe(nothrow($`xargs -0 grep something`))
      .pipe($`wc -l`)
    

    以下的包,無需匯入,直接使用

    chalk

    console.log(chalk.blue('Hello world!'))
    

    fs

    類似於如下的使用方式

    import {promises as fs} from 'fs'
    let content = await fs.readFile('./package.json')
    

    os

    await $`cd ${os.homedir()} && mkdir example`
    

    配置:

    $.shell

    指定要用的bash.

    $.shell = '/usr/bin/bash'
    

    $.quote

    指定用於在命令替換期間轉義特殊字元的函式

    預設用的是 shq 包.

    注意:

    __filename & __dirname這兩個變數是在commonjs中的。我們用的是.mjs結尾的es6 模組。

    在ESM模組中,Node.js 不提供__filename和 __dirname 全域性變數。 由於此類全域性變數在指令碼中非常方便,因此 zx 提供了這些以在 .mjs 檔案中使用(當使用 zx 可執行檔案時)

    require也是commonjs中的匯入模組方法,

    在 ESM 模組中,沒有定義 require() 函式。zx提供了 require() 函式,因此它可以與 .mjs 檔案中的匯入一起使用(當使用 zx 可執行檔案時)

    傳遞環境變數

    process.env.FOO = 'bar'
    await $`echo $FOO`
    

    傳遞陣列

    如果值陣列作為引數傳遞給 $,陣列的專案將被單獨轉義並通過空格連線

    Example:

    let files = [1,2,3]
    await $`tar cz ${files}`
    

    可以通過顯式匯入來使用 $ 和其他函式

    #!/usr/bin/env node
    import {$} from 'zx'
    await $`date`
    

    zx 可以將 .ts 指令碼編譯為 .mjs 並執行它們

    zx examples/typescript.ts
    

    總結

    到此這篇關於nodejs編寫bash指令碼享的文章就介紹到這了,更多相關nodejs編寫bash指令碼內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!