Heim  >  Artikel  >  Web-Frontend  >  Eine kurze Diskussion über verschiedene Optionen für NodeJS zum Ausführen von Bash-Skripten

Eine kurze Diskussion über verschiedene Optionen für NodeJS zum Ausführen von Bash-Skripten

青灯夜游
青灯夜游nach vorne
2021-07-08 11:12:023370Durchsuche

nodejsWie führe ich ein Bash-Skript aus? In diesem Artikel werden Ihnen verschiedene Optionen für den Knoten zum Ausführen von Bash-Skripten vorgestellt.

Eine kurze Diskussion über verschiedene Optionen für NodeJS zum Ausführen von Bash-Skripten

Vorwort

Ich habe kürzlich die Skriptsyntax von bash gelernt, aber wenn Sie mit der Bash-Syntax nicht vertraut sind, ist es sehr leicht, Fehler zu machen, zum Beispiel beim Anzeigen undefinierter Variablen shell Die Variablen in sind nicht definiert und können weiterhin verwendet werden, aber die Ergebnisse entsprechen möglicherweise nicht Ihren Erwartungen. Beispiel: 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' 肯定是会报错的,也就是说很多javascript编程经验无法复用到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. errorchild_process模块无法执行命令时,该对象不为空。例如,查找一个文件找不到该文件,则error对象不为空。但是,如果命令成功运行并将消息写入标准错误流,则该stderr对象不会为空。

当然我们可以使用同步的exec命令,execSync

// 引入 exec 命令 from child_process 模块
const { execSync } = require("child_process");

// 同步创建了一个hello的文件夹
execSync("mkdir hello");

Das Ergebnis ist, dass abc gedruckt wird, das Problem jedoch darin besteht, dass dieses Skript einen Fehler melden sollte. Der Variablen ist kein Wert zugewiesen, was einen Fehler darstellt.

Um diese Fehler auszugleichen, haben wir gelernt, am Anfang des Skripts Folgendes hinzuzufügen: set -u. Dieser Befehl bedeutet, dass das Skript ihn am Kopf hinzufügt und einen Fehler meldet Wenn Sie auf nicht vorhandene Variablen stoßen, stoppen Sie die Implementierung.
  • Führen Sie es erneut aus und es wird folgende Meldung angezeigt: test.sh: 3: test.sh: num: Parameter nicht festgelegt
  • Stellen Sie sich noch einmal vor, Sie wollten ursprünglich Folgendes löschen: rm -rf $dir/* und dann dir Was wird daraus, wenn es leer ist? rm -rf ist ein Löschbefehl. Wenn $dir leer ist, entspricht dies der Ausführung von rm -rf /*, der alle Dateien löscht und Dateien-Ordner. . . Dann ist Ihr System verschwunden. Ist dies das legendäre Löschen der Bibliothek und das Weglaufen? abc' wird definitiv einen Fehler melden, was bedeutet, dass ein Großteil der JavaScript-Programmiererfahrung nicht in bash wiederverwendet werden kann.
  • Später begann ich zu erforschen, wie toll es wäre, wenn ich das node-Skript anstelle von bash verwenden würde. Nach einem Tag harter Arbeit entdeckte ich nach und nach ein Artefakt, die der zx-Bibliothek gehört, keine Sorge, ich werde diese Bibliothek zunächst nicht vorstellen. code>-Skripte mit node, was derzeit der Mainstream ist, und Sie werden wissen, warum es ein Artefakt ist.
  • Knoten führt Bash-Skript aus: widerstrebende Lösung: child_process API

Zum Beispiel der Befehl exec in der API von child_process code>

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.*$/, '', file);
  shell.sed('-i', /.*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);
}

Hier muss beachtet werden, dass exec zunächst asynchron ist, viele unserer bash-Skriptbefehle jedoch synchron sind. Und beachten Sie: Das error-Objekt unterscheidet sich von stderrWenn das child_process-Modul den Befehl nicht ausführen kann , dieses Objekt Nicht leer. Wenn beispielsweise bei der Suche nach einer Datei die Datei nicht gefunden werden kann, ist das Fehlerobjekt nicht leer. Wenn der Befehl jedoch erfolgreich ausgeführt wird und eine Meldung in den Standardfehlerstrom schreibt, ist das Objekt stderr nicht leer.

Natürlich können wir den synchronen exec-Befehl execSync verwenden.
#!/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}
Lassen Sie uns kurz andere APIs von child_process vorstellen, die Bash-Befehle ausführen können.

spawn: Starten Sie ein Kind Prozess zum Ausführen von Befehlen

exec: Starten Sie einen untergeordneten Prozess, um Befehle auszuführen. Anders als spawn verfügt er über eine Rückruffunktion, um die Situation des untergeordneten Prozesses zu ermitteln.

execFile: Starten Sie einen untergeordneten Prozess, um eine ausführbare Datei auszuführen. Ähnlich wie bei Spawn besteht der Unterschied darin, dass die Javascript-Datei angegeben werden muss, die der untergeordnete Prozess ausführen muss. Der Unterschied zwischen exec und ececFile besteht darin, dass exec zum Ausführen von Befehlen und eexecFile zum Ausführen von Dateien geeignet ist.

【Empfohlenes Lernen: „

nodejs-Tutorial

“】

Knotenausführungs-Bash-Skript: Erweiterte Lösung für Shelljs

#!/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}

Aus dem obigen Code ist Shelljs wirklich eine sehr gute Lösung zum Schreiben von Bash-Skripten in NodeJS. Nun, wenn Sie Die Knotenumgebung kann nicht nach Belieben aktualisiert werden. Ich denke, ShellJS ist tatsächlich ausreichend.

Dann schauen wir uns den heutigen Protagonisten zx an. Der Start liegt bereits bei 17,4k.

zx-Bibliothek

Offizielle Website: https://www.npmjs.com/package/zx

Lass uns zuerst sehen, wie man sie verwendet

npm i -g zx

Was denkst du? Schreibst du nur Linux-Befehle? kann viele Bash-Syntax ignorieren, einfach js direkt verwenden, und seine Vorteile sind nicht auf diese beschränkt. Es gibt einige interessante Funktionen:

1. Unterstützt ts, kompiliert automatisch .ts in .mjs-Dateien eine höhere Version des Knotens. Es bietet Unterstützung für das Ende der Datei des es6-Moduls, d. h. diese Datei kann direkt importiert werden, ohne sie mit anderen Tools zu umgehen. 🎜🎜 3. Es wird mit der Fetch-Bibliothek geliefert, die Netzwerkanfragen stellen kann. Mit der Chalk-Bibliothek können Sie farbige Schriftarten drucken, und sie verfügt über eine Fehlerbehandlungsmethode. Wenn der Bash-Befehl einen Fehler macht, können Sie ihn darin einschließen Methode und ignorieren Sie den Fehler🎜🎜🎜Vollständiges chinesisches Dokument (die Übersetzung unten ist durchschnittlich, bitte verzeihen Sie mir)🎜🎜
Node.js >= 14.8.0
🎜 Bash ist großartig, aber wenn es um Skripterstellung geht, wählen die Leute oft eine bequemere Programmiersprache. JavaScript ist eine perfekte Wahl, aber die Standard-Node.js-Bibliotheken erfordern ein paar zusätzliche Dinge, bevor sie verwendet werden können. zx basiert auf child_process, maskiert Argumente und stellt sinnvolle Standardwerte bereit. 🎜🎜Installation🎜
#!/usr/bin/env zx
现在您将能够像这样运行您的脚本:

chmod +x ./script.mjs
./script.mjs
🎜Erforderliche Umgebung🎜
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 exitCode: Promise<number>
  pipe(dest): ProcessPromise<T>
}

pipe() 方法可用于重定向标准输出:

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

阅读更多的关于管道的信息:https://github.com/google/zx/blob/HEAD/examples/pipelines.md

ProcessOutputtypescript接口定义

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

函数:

cd()

更改当前工作目录

cd(&#39;/tmp&#39;)
await $`pwd` // outputs /tmp

fetch()

node-fetch 包。

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

question()

readline包

let bear = await question(&#39;What kind of bear is best? &#39;)
let token = await question(&#39;Choose env variable: &#39;, {
  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(&#39;Hello world!&#39;))

fs

类似于如下的使用方式

import {promises as fs} from &#39;fs&#39;
let content = await fs.readFile(&#39;./package.json&#39;)

os

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

配置:

$.shell

指定要用的bash.

$.shell = &#39;/usr/bin/bash&#39;

$.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 = &#39;bar&#39;await $`echo $FOO`

传递数组

如果值数组作为参数传递给 $,数组的项目将被单独转义并通过空格连接 Example:

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

可以通过显式导入来使用 $ 和其他函数

#!/usr/bin/env nodeimport {$} from 'zx'await $`date`复制代码

zx 可以将 .ts 脚本编译为 .mjs 并执行它们

zx examples/typescript.ts

更多编程相关知识,请访问:编程视频!!

Das obige ist der detaillierte Inhalt vonEine kurze Diskussion über verschiedene Optionen für NodeJS zum Ausführen von Bash-Skripten. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen