萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 作業系統 >  >> Linux 教學

Node.js 在 Linux 執行系統指令完整指南:child_process 模組實戰

Node.js child_process 模組提供四種衍生子進程的方式,讓 JavaScript 能在伺服器端執行 Shell 指令、啟動外部程式、建立管道通訊。本文詳細解析 exec、execFile、spawn、fork 四大 API 的適用場景、參數細節、錯誤處理與安全最佳實踐。

一、四大 API 選型指南

child_process 架構

API回傳適用場景特性
exec()Promise/Callback (stdout/stderr 緩衝)簡單 Shell 指令、短期執行、需完整輸出啟動 Shell 解析、緩衝區預設 1MB、支援管道/重導向
execFile()Promise/Callback直接執行可執行檔 (無 Shell 解析)較輕量、避免 Shell 注入、需指定完整路徑
spawn()ChildProcess (Stream)長期運行、大量輸出、串流處理、雙向通訊不緩衝輸出、標準流為 Stream、支援自訂 stdio
fork()ChildProcess (IPC)Node.js 子進程、多核並行、傳遞物件訊息建立 IPC 通道、process.send()/message 事件、獨立 V8 實例

二、exec():Shell 指令執行

const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);

// 基本用法 (Callback)
exec('pwd', (error, stdout, stderr) => {
    if (error) { console.error(`exec error: ${error}`); return; }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
});

// Promise/async-await 風格 (推薦)
async function runCmd() {
    try {
        const { stdout, stderr } = await execPromise('ls -la /tmp');
        console.log(stdout);
    } catch (err) {
        console.error(`Command failed: ${err.message}`);
        console.error(`stderr: ${err.stderr}`);
    }
}

// 複雜 Shell 指令 (管道、重導向、變數)
await execPromise('cd /var/log && grep -i error *.log | wc -l');

// 選項控制
await execPromise('sleep 10', {
    timeout: 5000,           // 超時毫秒數,超時殺死進程
    maxBuffer: 1024 * 1024,  // stdout/stderr 最大緩衝 (預設 1MB)
    cwd: '/home/user',       // 工作目錄
    env: { ...process.env, NODE_ENV: 'production' }, // 環境變數
    shell: '/bin/bash'       // 指定 Shell (預設 /bin/sh)
});

三、execFile():直接執行可執行檔

const { execFile } = require('child_process');
const util = require('util');
const execFilePromise = util.promisify(execFile);

// 執行腳本檔 (需 Shebang + 執行權限)
await execFilePromise('/opt/scripts/backup.sh', ['--full', '--dest=/backup']);

// 執行二進位程式帶參數
await execFilePromise('/usr/bin/docker', ['ps', '--format', 'json']);

// Windows 批次檔
await execFilePromise('cmd.exe', ['/c', 'D:\\scripts\\build.bat']);

// 選項
await execFilePromise('/path/to/app', ['arg1', 'arg2'], {
    cwd: '/working/dir',
    timeout: 30000,
    windowsHide: true  // Windows 隱藏視窗
});

四、spawn():串流與長期進程

const { spawn } = require('child_process');

// 即時處理標準輸出流
const find = spawn('find', ['/var/log', '-name', '*.log', '-mtime', '-7']);
let output = '';

find.stdout.on('data', (chunk) => {
    output += chunk.toString();
    // 即時處理每一行
    chunk.toString().split('\n').forEach(line => {
        if (line) console.log(`[FOUND] ${line}`);
    });
});

find.stderr.on('data', (data) => {
    console.error(`stderr: ${data}`);
});

find.on('close', (code) => {
    console.log(`find exited with code ${code}`);
    console.log(`Total output length: ${output.length}`);
});

// 雙向通訊範例:互動式程式 (如 python3 -i)
const python = spawn('python3', ['-i'], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
python.stdin.write("print('hello from node')\n");
python.stdin.end();

stdio 選項進階控制

// 選項:'pipe' (預設), 'ignore', 'inherit', 'ipc', Stream, 整數 fd
const child = spawn('node', ['worker.js'], {
    stdio: [
        'pipe',    // stdin  -> 父進程可寫入
        'pipe',    // stdout -> 父進程可讀取
        'inherit', // stderr -> 直接繼承父進程 stderr (終端機顯示)
        'ipc'      // fd 3: IPC 通道 (需 fork 或 node --enable-ipc)
    ]
});

五、fork():Node.js 多進程並行

// master.js
const { fork } = require('child_process');
const cpus = require('os').cpus().length;

for (let i = 0; i < cpus; i++) {
    const worker = fork('./worker.js', [i]);
    worker.on('message', (msg) => {
        console.log(`Worker ${msg.workerId} finished: ${msg.result}`);
    });
    worker.send({ taskId: i, data: generateTaskData(i) });
}

// worker.js
process.on('message', (task) => {
    const result = heavyComputation(task.data);
    process.send({ workerId: task.taskId, result });
});

六、安全最佳實踐:防止命令注入

// 安全包裝器範例
const { execFile } = require('child_process');

async function safeExec(file, args = [], options = {}) {
    // 白名單驗證
    const allowed = ['ls', 'cat', 'grep', 'docker', 'kubectl'];
    if (!allowed.includes(file)) throw new Error(`Command not allowed: ${file}`);

    // 參數清理:移除控制字元
    const cleanArgs = args.map(arg => String(arg).replace(/[\x00-\x1F\x7F]/g, ''));

    return execFile(file, cleanArgs, { ...options, shell: false });
}

七、常見錯誤與排查

風險寫法安全寫法原理
exec('ls ' + userInput)execFile('ls', [userInput])繞過 Shell 解析,參數直接傳給 execve
exec('cmd ' + arg)exec('cmd', [arg]) (Node 18+)陣列形式自動跳脫
使用者輸入含 ; rm -rf /嚴格驗證/白名單/跳脫shell-escape 套件或手動正則
錯誤原因解決
ENOENT指令/路徑不存在使用 which 或 command -v 驗證,指定完整路徑
EACCES權限不足chmod +x、檢查檔案系統 noexec 掛載選項
E2BIG參數過長改用 spawn 串流、或寫入暫存檔
maxBuffer exceeded輸出超過 1MB增大 maxBuffer 或改用 spawn
殭屍進程未監聽 close/exit必須處理退出事件,或使用 detached: true 獨立進程組

掌握 child_process 四大 API 的適用邊界、串流處理模式、安全防注入原則,能讓 Node.js 安全、高效地作為系統自動化、DevOps 工具、微服務閘道的編排中樞。

copyright © 萬盛學電腦網 all rights reserved