使用Node.js执行Shell命令

Executing Shell Commands with Node.js

介绍

系统管理员和开发人员经常转向自动化,以减少工作量并改善流程。 使用服务器时,自动化任务通常使用Shell脚本编写脚本。 但是,开发人员可能更喜欢对复杂任务使用更通用的高级语言。 许多应用程序还需要与文件系统和其他OS级别的组件进行交互,这通常可以通过命令行级别的实用程序更轻松地完成。

使用Node.js,我们可以运行Shell命令并使用JavaScript处理它们的输入和输出。 因此,我们可以使用JavaScript而不是Shell脚本语言来编写大多数这些复杂的操作,从而可能使程序更易于维护。

在本文中,我们将学习使用child_process模块在Node.js中执行shell命令的各种方法。

child_proccess模块

Node.js在单个线程中执行其主事件循环。 但是,这并不意味着其所有处理都在一个线程中完成。 Node.js中的异步任务在其他内部线程中执行。 完成后,回调或错误中的代码将返回到主单线程。

这些不同的线程在同一Node.js进程中运行。 但是,有时需要创建另一个进程来执行代码。 创建新进程后,操作系统将确定使用哪个处理器以及如何安排其任务。

child_process模块在我们的主要Node.js进程中创建新的子进程。 我们可以使用这些子进程执行shell命令。

如果使用正确,则使用外部进程可以提高应用程序的性能。 例如,如果Node.js应用程序的功能占用大量CPU,则由于Node.js是单线程的,因此它将阻止其他任务在运行时执行。

但是,我们可以将资源密集型代码委托给子进程,比如说一个非常有效的C ++程序。 然后,我们的Node.js代码将在一个新的过程中执行该C ++程序,而不阻止其其他活动,并在完成过程后阻止其输出。

我们将用于执行Shell命令的两个函数是execspawn

exec函数

exec()函数创建一个新的shell并执行给定的命令。 执行的输出被缓冲,这意味着保留在内存中,并且可以在回调中使用。

让我们使用exec()函数列出当前目录中的所有文件夹和文件。 在名为lsExec.js的新Node.js文件中,编写以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
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}`);
});

首先,我们需要程序中的child_process模块,特别是使用exec()函数(通过ES6解构)。 接下来,我们使用两个参数调用exec()函数:

  • 我们要执行的带有shell命令的字符串。

  • 具有三个参数的回调函数:errorstdoutstderr

  • 我们正在运行的shell命令是ls -la,该命令应逐行列出当前目录中的所有文件和文件夹,包括隐藏的文件/文件夹。 回调函数记录在尝试执行命令或在shell的stdoutstderr流上输出时是否得到error

    注意:error对象与stderr不同。 当child_process模块无法执行命令时,error对象不为null。 例如,如果尝试在exec()中执行另一个Node.js脚本,但是找不到该文件,则可能会发生这种情况。 另一方面,如果命令成功运行并将消息写入标准错误流,则stderr对象将不会为null。

    如果运行该Node.js文件,则应该看到类似于以下内容的输出:

    1
    2
    3
    4
    5
    6
    7
    $ node lsExec.js
    stdout: total 0
    <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e38791949b91ce9b91ce9ba3">[emailprotected]</a> 9 arpan arpan  0 Dec  7 00:14 .
    <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4125333639336c39336c3901">[emailprotected]</a> 4 arpan arpan  0 Dec  7 22:09 ..
    <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="1f326d68326d32326d32325f">[emailprotected]</a> 1 arpan arpan  0 Dec  7 15:10 lsExec.js

    child process exited with code 0

    现在,我们已经了解了如何使用exec()运行命令,让我们学习另一种使用spawn()执行命令的方法。

    产生功能

    spawn()函数在新进程中执行命令。 此函数使用Stream API,因此可通过侦听器使用命令的输出。

    与之前类似,我们将使用spawn()函数列出当前目录中的所有文件夹和文件。 让我们创建一个新的Node.js文件lsSpawn.js,然后输入以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const { spawn } = require("child_process");

    const ls = spawn("ls", ["-la"]);

    ls.stdout.on("data", data => {
        console.log(`stdout: ${data}`);
    });

    ls.stderr.on("data", data => {
        console.log(`stderr: ${data}`);
    });

    ls.on('error', (error) => {
        console.log(`error: ${error.message}`);
    });

    ls.on("close", code => {
        console.log(`child process exited with code ${code}`);
    });

    我们首先需要child_process模块中的spawn()函数。 然后,我们创建一个执行ls命令的新进程,并将-la作为参数传递。 请注意,参数如何保存在数组中而不包含在命令字符串中。

    然后,我们设置听众。 lsstdout对象在命令写入该流时触发data事件。 同样,当命令写入该流时,stderr也将触发一个data事件。

    通过直接在存储命令引用的对象上侦听错误来捕获错误。 仅当child_process无法运行命令时才会出现错误。

    close事件在命令完成时发生。

    如果运行此Node.js文件,则应该使用exec()像以前一样获得输出:

    1
    2
    3
    4
    5
    6
    7
    8
    $ node lsSpawn.js
    stdout: total 0
    <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="cda9bfbab5bfe0b5bfe0b58d">[emailprotected]</a> 9 arpan arpan  0 Dec  7 00:14 .
    <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="395d4b4e414b14414b144179">[emailprotected]</a> 4 arpan arpan  0 Dec  7 22:09 ..
    <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="406d32376d326d6d326d6d00">[emailprotected]</a> 1 arpan arpan  0 Dec  7 15:10 lsExec.js
    <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="e2cf9095cf90cfcf90cfcfa2">[emailprotected]</a> 1 arpan arpan  0 Dec  7 15:40 lsSpawn.js

    child process exited with code 0

    何时使用exec和spawn?

    exec()spawn()之间的主要区别在于它们如何返回数据。 由于exec()将所有输出存储在缓冲区中,因此与spawn()相比,内存占用更多的内存,而spawn()随即输出输出。

    通常,如果您不希望返回大量数据,则可以使用exec()为简单起见。 用例的好例子是创建文件夹或获取文件状态。 但是,如果期望命令产生大量输出,则应使用spawn()。 一个很好的例子是使用命令来处理二进制数据,然后将其加载到Node.js程序中。

    结论

    Node.js可以使用标准的child_process模块运行shell命令。 如果使用exec()函数,则命令将运行,并且其输出将在回调中提供给我们。 如果使用spawn()模块,则其输出将通过事件侦听器提供。

    如果我们的应用程序期望从命令中获得大量输出,那么我们应该首选spawn()而不是exec()。 如果不是,出于简单起见,我们可能选择使用exec()

    现在您可以运行Node.js外部的任务,那么您将构建哪些应用程序-