Home  >  Article  >  Web Front-end  >  A brief discussion on several options for nodejs to execute bash scripts

A brief discussion on several options for nodejs to execute bash scripts

青灯夜游
青灯夜游forward
2021-07-08 11:12:023388browse

nodejsHow to execute bash script? This article will introduce to you several options for node to execute bash scripts.

A brief discussion on several options for nodejs to execute bash scripts

Preface

Recently I am learning bash script syntax, but if you are not familiar with bash syntax, It feels very error-prone, for example: Display undefined variables shell The variables in are not defined and can still be used, but the results may not be what you expected. For example:

#!/bin/bash

# 这里是判断变量var是否等于字符串abc,但是var这个变量并没有声明
if [ "$var" = "abc" ] 
then
   # 如果if判断里是true就在控制台打印 “ not abc”
   echo  " not abc" 
else
   # 如果if判断里是false就在控制台打印 “ abc”
   echo " abc "
fi

The result is that abc is printed, but the problem is that this script should report an error. The variable is not assigned a value, which is an error.

In order to make up for these errors, we learn to add at the beginning of the script: set -uThis command means that the script adds it to the head, and an error will be reported when encountering non-existent variables. , and stop execution.

When you run it again, you will be prompted: test.sh: 3: test.sh: num: parameter not set

Imagine again that you originally wanted to delete:rm -rf $dir /*Then when dir is empty, what does it become? rm -rf is a delete command. If $dir is empty, it is equivalent to executing rm -rf /*, which is to delete all files and folders. . . Then, your system will be gone. Is this the legendary deletion of the database and running away~~~~

If it is node or browser environment, we will directly var = == 'abc' will definitely report an error, which means that a lot of javascript programming experience cannot be reused in bash. It would be great if it could be reused.

Later I started to explore, how great it would be if node script was used instead of bash. After a day of tossing, I gradually discovered an artifact, owned by Google The zx library, don’t worry, I won’t introduce this library first. Let’s first look at how the current mainstream node is used to write bash scripts, and you will know why. It's an artifact.

node executes bash script: reluctant solution: child_process API

For example, execcommand## in the API of child_process #

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}`);
});

What needs to be noted here is that first

exec is asynchronous, but many of our bash script commands are synchronous.

And note: The

error object is different from stderr. errorWhen the child_process module cannot execute the command, this object not null. For example, if searching for a file cannot find the file, the error object is not empty. However, if the command runs successfully and writes a message to the standard error stream, the stderr object will not be empty.

Of course we can use the synchronous

exec command, execSync

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

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

Let’s briefly introduce the other APIs of child_process that can execute bash commands

    spawn: Start a child process to execute the command
  • exec: Start a child process to execute the command. Different from spawn, it has a callback function to know the situation of the child process
  • execFile: Start a child process to execute the executable file
  • fork: similar to spawn, the difference is that it needs to specify the javascript file that the child process needs to execute
  • ## The difference between #exec and ececFile is that exec is suitable for executing commands and eexecFile is suitable for executing files.

[Recommended learning: "

nodejs Tutorial

"]

node executes bash script: advanced solution shelljs

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);
}
from From the above code, shelljs is really a very good solution for writing bash scripts in nodejs. If your node environment cannot be upgraded at will, I think shelljs is indeed enough.

Then let’s take a look at today’s protagonist zx. The start is already 17.4k.

zx library

Official website: https://www.npmjs.com/package/zx

Let’s see how to use it first

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

What do you think? Are you just writing Linux commands? You can ignore a lot of bash syntax and just use js directly. Moreover, its advantages are not limited to these. There are some interesting features:

1. Support ts, and automatically compile .ts into .mjs files. The .mjs file is the end of the file that comes with higher versions of node and supports es6 module. That is, this file can directly import the module without escaping it with other tools

2. Comes with support for pipeline operation pipe method

3. Comes with fetch library, which can make network requests. Comes with chalk library, which can print colored fonts. Comes with error handling nothrow method. If the bash command makes an error, you can wrap it in this method and ignore the error

Complete Chinese document (the translation below is average, please forgive me)

#!/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 is great, but in When writing scripts, people often choose a programming language that is more convenient. JavaScript is a perfect choice, but the standard Node.js library requires a few extra things before you can use it. zx is based on child_process , escaping arguments and providing sensible defaults.

Installation

npm i -g zx

Required environment

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

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

The above is the detailed content of A brief discussion on several options for nodejs to execute bash scripts. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:juejin.cn. If there is any infringement, please contact admin@php.cn delete