Node.js 的 os 模块 提供了一系列与操作系统交互的实用方法和属性。它允许 Node.js 应用程序获取关于底层操作系统的信息,例如平台、架构、CPU 信息、内存使用情况、网络接口以及处理操作系统特定的文件路径等。os 模块是一个内置模块,无需安装,通过 require('os') 即可使用。

核心思想:提供跨平台的方式来获取和操作系统相关的环境信息。 开发者可以利用这些信息编写出更具平台适应性的应用程序。


一、为什么需要 os 模块?

在 Node.js 应用程序开发中,有时需要根据运行环境的不同来调整程序的行为,或者获取系统资源的使用情况进行监控。JavaScript 语言本身是平台无关的,无法直接访问底层操作系统信息。os 模块正是为了填补这一空白,提供了以下核心能力:

  • 平台适配:根据不同的操作系统(Windows, Linux, macOS)调整文件路径、执行特定命令或加载特定配置。
  • 资源监控:获取 CPU 使用率、内存使用量、系统运行时间等,用于性能监控或诊断。
  • 网络配置:查看机器的网络接口信息,如 IP 地址、MAC 地址。
  • 用户与环境信息:获取当前用户信息、临时文件目录、Home 目录等。
  • 错误处理与信号:提供操作系统定义的错误码和进程信号常量。

二、os 模块的关键属性和方法

通过 const os = require('os'); 引入 os 模块后,即可访问其提供的各种功能。

2.1 系统信息

这一类方法提供了关于操作系统通用信息的属性和方法。

  • os.platform(): 返回 Node.js 进程运行的操作系统平台标识符。
    • 返回值示例'darwin' (macOS), 'linux', 'win32' (Windows)。
  • os.arch(): 返回 Node.js 进程运行的 CPU 架构。
    • 返回值示例'x64', 'arm64', 'ia32'
  • os.hostname(): 返回操作系统的主机名。
  • os.type(): 返回操作系统名称。
    • 返回值示例'Linux', 'Darwin' (macOS), 'Windows_NT'.
  • os.release(): 返回操作系统发行版本号。
    • 返回值示例'18.6.0' (Linux), '22.0.0' (macOS Sonoma), '10.0.19045' (Windows 10).
  • os.version(): 返回操作系统内核版本字符串。
    • 返回值示例'#1 SMP PREEMPT_DYNAMIC Wed Nov 15 13:41:06 UTC 2023' (Linux), 'Darwin Kernel Version 22.0.0: Thu Sep 22 17:09:47 PDT 2022; root:xnu-8792.20.119~2/RELEASE_ARM64_T6000' (macOS).
  • os.uptime(): 返回系统的总运行时间,以秒为单位。注意:这与 process.uptime() 不同,后者返回 Node.js 进程的运行时间。
  • os.EOL: 返回操作系统特定的行末(End-Of-Line)标记。
    • 返回值示例'\n' (POSIX/Linux/macOS), '\r\n' (Windows)。这在处理文本文件时非常有用,可以确保跨平台兼容性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const os = require('os');

console.log('--- 系统信息 ---');
console.log(`平台: ${os.platform()}`);
console.log(`架构: ${os.arch()}`);
console.log(`主机名: ${os.hostname()}`);
console.log(`操作系统类型: ${os.type()}`);
console.log(`操作系统发行版本: ${os.release()}`);
console.log(`操作系统内核版本: ${os.version()}`);
console.log(`系统总运行时间: ${os.uptime()} 秒`);
console.log(`行末标记 (EOL): "${os.EOL.replace(/\n/g, '\\n').replace(/\r/g, '\\r')}"`);

// 跨平台文件写入示例
const fs = require('fs');
const content = `Hello, Node.js!${os.EOL}This is a new line.${os.EOL}`;
fs.writeFileSync('output.txt', content);
console.log('已写入 output.txt,使用了操作系统特定的行末标记。');

2.2 CPU 信息

  • os.cpus(): 返回一个数组,其中包含每个 CPU 核心的信息。每个核心对象都包含 model (型号)、speed (CPU 速度,MHz) 和 times (一个包含用户、系统、空闲等时间的毫秒数对象)。

    • times 属性详细:
      • user: 用户模式下的 CPU 时间。
      • nice: 低优先级用户模式下的 CPU 时间。
      • sys: 系统模式(内核)下的 CPU 时间。
      • idle: 空闲时间。
      • irq: 硬中断时间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
const os = require('os');

console.log('\n--- CPU 信息 ---');
const cpus = os.cpus();
console.log(`CPU 核心数: ${cpus.length}`);
cpus.forEach((cpu, index) => {
console.log(` 核心 ${index + 1}:`);
console.log(` 型号: ${cpu.model}`);
console.log(` 速度: ${cpu.speed} MHz`);
console.log(` 运行时间 (ms):`);
console.log(` 用户: ${cpu.times.user}`);
console.log(` 系统: ${cpu.times.sys}`);
console.log(` 空闲: ${cpu.times.idle}`);
});

// 计算 CPU 负载的简单示例 (两次采样之间的 CPU 变化)
function getCpuLoadAverage(sampleInterval = 1000) {
const start = os.cpus();
return new Promise(resolve => {
setTimeout(() => {
const end = os.cpus();
let totalIdle = 0;
let totalTick = 0;

for (let i = 0; i < start.length; i++) {
const idleDiff = end[i].times.idle - start[i].times.idle;
const userDiff = end[i].times.user - start[i].times.user;
const sysDiff = end[i].times.sys - start[i].times.sys;
const niceDiff = end[i].times.nice - start[i].times.nice;
const irqDiff = end[i].times.irq - start[i].times.irq;

totalIdle += idleDiff;
totalTick += idleDiff + userDiff + sysDiff + niceDiff + irqDiff;
}

const cpuLoad = (1 - totalIdle / totalTick) * 100;
resolve(cpuLoad.toFixed(2));
}, sampleInterval);
});
}

// (async () => {
// const cpuLoad = await getCpuLoadAverage();
// console.log(`\n过去1秒的CPU负载: ${cpuLoad}%`);
// })();

2.3 内存信息

  • os.totalmem(): 返回系统总内存量,以字节为单位。
  • os.freemem(): 返回系统可用(空闲)内存量,以字节为单位。
1
2
3
4
5
6
7
8
9
10
11
const os = require('os');

console.log('\n--- 内存信息 ---');
const totalMemoryBytes = os.totalmem();
const freeMemoryBytes = os.freemem();

const bytesToGB = (bytes) => (bytes / 1024 / 1024 / 1024).toFixed(2);

console.log(`总内存: ${bytesToGB(totalMemoryBytes)} GB`);
console.log(`空闲内存: ${bytesToGB(freeMemoryBytes)} GB`);
console.log(`已使用内存: ${bytesToGB(totalMemoryBytes - freeMemoryBytes)} GB`);

2.4 网络信息

  • os.networkInterfaces(): 返回一个对象,其中包含机器上每个网络接口的信息。对象的键是接口名称(例如 'eth0', 'lo', 'Wi-Fi'),值是一个数组,数组中每个元素是一个描述该接口地址的对象。

    • 每个地址对象包含:
      • address: IP 地址。
      • netmask: 网络掩码。
      • family: IP 地址族,'IPv4''IPv6'
      • mac: MAC 地址。
      • internal: 一个布尔值,如果接口是内部的(例如回环地址 127.0.0.1),则为 true
      • cidr: CIDR 记法表示的地址。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const os = require('os');

console.log('\n--- 网络接口信息 ---');
const networkInterfaces = os.networkInterfaces();

for (const name in networkInterfaces) {
if (Object.prototype.hasOwnProperty.call(networkInterfaces, name)) {
const iface = networkInterfaces[name];
console.log(`\n接口名称: ${name}`);
iface.forEach((details) => {
console.log(` 地址: ${details.address}`);
console.log(` 家族: ${details.family}`);
console.log(` MAC 地址: ${details.mac}`);
console.log(` 内部接口: ${details.internal}`);
console.log(` CIDR: ${details.cidr}`);
});
}
}

// 获取本机的 IPv4 地址 (排除回环地址)
function getLocalIpAddress() {
const interfaces = os.networkInterfaces();
for (const name in interfaces) {
if (Object.prototype.hasOwnProperty.call(interfaces, name)) {
for (const iface of interfaces[name]) {
if (iface.family === 'IPv4' && !iface.internal) {
return iface.address;
}
}
}
}
return '127.0.0.1'; // 如果找不到非内部 IPv4 地址,则返回回环地址
}
console.log(`\n本机 IPv4 地址: ${getLocalIpAddress()}`);

2.5 用户信息

  • os.userInfo([options]): 返回一个包含当前用户信息(如 usernameuidgidhomedirshell)的对象。
    • options: 可选参数,{ encoding: 'utf8' } 可指定编码。
    • 在 Windows 上,uidgid 通常为 -1
1
2
3
4
5
6
7
8
9
const os = require('os');

console.log('\n--- 用户信息 ---');
const userInfo = os.userInfo();
console.log(`用户名: ${userInfo.username}`);
console.log(`用户ID (UID): ${userInfo.uid}`);
console.log(`组ID (GID): ${userInfo.gid}`);
console.log(`Home 目录: ${userInfo.homedir}`);
console.log(`Shell: ${userInfo.shell}`);

2.6 目录路径

  • os.homedir(): 返回当前用户的主目录路径。
  • os.tmpdir(): 返回操作系统默认的临时文件目录路径。
1
2
3
4
5
6
7
8
9
10
11
12
13
const os = require('os');

console.log('\n--- 目录路径 ---');
console.log(`用户主目录: ${os.homedir()}`);
console.log(`临时文件目录: ${os.tmpdir()}`);

// 示例:在临时目录创建临时文件
const path = require('path');
const tempFilePath = path.join(os.tmpdir(), `my-temp-file-${Date.now()}.txt`);
fs.writeFileSync(tempFilePath, '这是临时文件内容。');
console.log(`已在临时目录创建文件: ${tempFilePath}`);
// 注意:实际应用中,应在不再需要时删除临时文件
// fs.unlinkSync(tempFilePath);

2.7 操作系统特定的常量 (os.constants)

os.constants 对象包含了操作系统定义的常量,主要分为两类:

  • os.constants.errno: 包含了标准的 POSIX 错误码(例如 EACCESENOENT)。
  • os.constants.signals: 包含了标准的操作系统进程信号(例如 SIGHUPSIGINTSIGTERMSIGKILL)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const os = require('os');

console.log('\n--- 操作系统常量 ---');
console.log('部分 errno 错误码:');
console.log(` EACCES (权限拒绝): ${os.constants.errno.EACCES}`);
console.log(` ENOENT (文件或目录不存在): ${os.constants.errno.ENOENT}`);

console.log('部分进程信号:');
console.log(` SIGHUP (挂断): ${os.constants.signals.SIGHUP}`);
console.log(` SIGINT (中断,通常是 Ctrl+C): ${os.constants.signals.SIGINT}`);
console.log(` SIGTERM (终止): ${os.constants.signals.SIGTERM}`);

// 示例:在 child_process 中使用信号
// const { spawn } = require('child_process');
// const ls = spawn('sleep', ['10']); // 启动一个耗时任务

// setTimeout(() => {
// console.log(`\n发送 SIGTERM 信号给 sleep 进程 (PID: ${ls.pid})`);
// ls.kill(os.constants.signals.SIGTERM);
// }, 2000);

// ls.on('close', (code) => {
// console.log(`sleep 进程以代码 ${code} 退出`);
// });

三、最佳实践与安全性考虑

  1. 跨平台兼容性

    • 始终使用 os.EOL 来处理文本文件中的换行符,而不是硬编码 \n\r\n
    • 在处理文件路径时,推荐使用 path 模块 (path.join(), path.sep) 来替代手动拼接,以确保在不同操作系统上路径格式的正确性。
    • 根据 os.platform() 的返回值来执行平台特定的逻辑(例如,在 Windows 上使用 .bat 脚本,在 Linux/macOS 上使用 .sh 脚本)。
  2. 资源监控

    • os.totalmem()os.freemem() 提供了系统级别的内存信息,可用于监控服务器的整体健康状况。
    • os.cpus()times 属性可以用来计算 CPU 的实时负载。通常需要两次采样,计算时间差来得出 CPU 忙碌的百分比。
    • 注意:这些方法提供的是瞬时快照,要实现持续监控,需要定期采样。
  3. 安全性

    • os.userInfo() 会暴露当前用户的用户名、主目录和 shell 信息。在某些安全敏感的环境中,应谨慎处理这些信息,避免不必要的暴露。
    • os 模块本身不会引入直接的安全漏洞,但通过其获取的信息(如网络接口 IP 地址)如果被不当使用,可能会间接导致问题。
  4. 性能影响

    • os 模块的方法通常是同步的,但它们的执行速度非常快,对应用程序的性能影响微乎其微。频繁调用(例如,在高性能循环中每毫秒调用一次)仍可能造成轻微开销,但在大多数场景下无需担心。

四、总结

Node.js 的 os 模块是一个强大而基础的工具,它使得 JavaScript 应用程序能够深入了解并适应其运行的操作系统环境。无论是构建跨平台工具、开发系统监控应用、优化资源使用,还是仅仅需要获取当前机器的基本信息,os 模块都提供了丰富且易于使用的接口。合理利用 os 模块,可以显著提高 Node.js 应用程序的健壮性、灵活性和可移植性。