Skip to content

Node.js运行时

Node.js 运行脚本时需要和进程、标准输出、退出码、环境变量交互,运行时部分重点放在这些命令行行为上。

一、版本和入口

生产环境一般只跟 LTS。Current 版本适合试新特性,长期服务跟着 Current 走,升级时更容易被原生依赖绊一下。

bash
node -v        # 查看 Node.js 版本
npm -v         # npm 随 Node.js 一起安装
node -p "1+1"  # -p 会打印表达式结果,排查环境时很方便

一个脚本入口文件通常叫 index.jsmain.js 或者按用途命名。

js
// index.js
console.log("script started");
bash
node index.js

Node.js 从入口文件开始执行,再按 importrequire 继续加载其它文件。小脚本入口名我倾向按用途起,比如 check-service.js,以后翻目录时更容易想起来它是干什么的。

二、process 对象

process 表示当前 Node.js 进程。

js
console.log(process.version);  // Node.js 版本
console.log(process.platform); // 当前系统平台,比如 linux、win32
console.log(process.arch);     // CPU 架构,比如 x64、arm64
console.log(process.pid);      // 当前进程 ID

当前工作目录用 process.cwd()

js
console.log(process.cwd()); // 定时任务和 systemd 里常看这个,路径问题多半从这里查

这个值不等于脚本文件所在目录。systemd、crontab、CI 里最容易因为工作目录不同读错相对路径。

三、命令行参数

命令行参数在 process.argv 里。

js
// args.js
console.log(process.argv);
bash
node args.js nginx active

前两个元素固定是 node 路径和脚本路径,业务参数从第 3 个开始取。

js
const [, , service, status] = process.argv; // 跳过前两个固定参数

if (!service || !status) {
  console.error("usage: node check.js <service> <status>");
  process.exit(1); // 参数缺失时明确失败,方便外部调度系统识别
}

console.log(`${service}\t${status}`);

参数不超过两三个时,读 process.argv 就够。参数一多就换 commander 或 yargs,不然手写解析很快会把脚本主体挤乱。

四、标准输出和错误输出

正常结果走 stdout,错误信息走 stderr。

js
console.log("service active");       // stdout
console.error("service inactive");   // stderr

Linux 里可以分开重定向。

bash
node check.js > result.log 2> error.log

脚本给其它工具调用时,这个习惯很重要。stdout 放结果,stderr 放报错,后面接管道或收日志都省事。

js
const result = {
  service: "nginx",
  active: true
};

console.log(JSON.stringify(result)); // stdout 只放最终结果,方便 jq 继续处理

五、退出码

退出码 0 表示成功,非 0 表示失败。

js
const active = false;

if (!active) {
  console.error("nginx is inactive");
  process.exit(2); // 可以用不同退出码区分失败类型
}

console.log("nginx is active");

入口处统一兜底异常。

js
function main() {
  throw new Error("check failed");
}

try {
  main();
} catch (err) {
  console.error(err.message); // 普通脚本输出里只保留可读错误
  process.exit(1);
}

CI、crontab、systemd 都会看退出码。只打印错误但返回 0,后续会被误判成功。

六、环境变量

环境变量从 process.env 里读,读出来永远是字符串或 undefined

js
const port = Number(process.env.PORT || 3000); // 环境变量没有时给默认值
const logLevel = process.env.LOG_LEVEL || "info";

console.log({ port, logLevel });

必填变量要检查。

js
function requireEnv(name) {
  const value = process.env[name];

  if (!value) {
    throw new Error(`${name} is required`); // 缺配置时尽早失败
  }

  return value;
}

const token = requireEnv("API_TOKEN");
console.log(`token length=${token.length}`);

Linux 里临时传变量:

bash
PORT=8080 LOG_LEVEL=debug node server.js

PowerShell 里是另一套写法:

powershell
$env:PORT=8080
$env:LOG_LEVEL="debug"
node server.js

密钥不进代码仓库。.env.example 可以提交,真实 .env 放进 .gitignore。内部工具也按这个习惯来,虽说前期麻烦一点,但迁到 CI 或容器时不用再补债。

七、当前脚本路径

ESM 里没有 __dirname,需要从 import.meta.url 转。

js
import { fileURLToPath } from "node:url";
import path from "node:path";

const currentFile = fileURLToPath(import.meta.url); // 当前 JS 文件的绝对路径
const currentDir = path.dirname(currentFile);       // 当前 JS 文件所在目录

console.log(currentFile);
console.log(currentDir);

读取随脚本一起发布的模板文件时,用脚本目录更稳。

js
import { fileURLToPath } from "node:url";
import path from "node:path";

const currentDir = path.dirname(fileURLToPath(import.meta.url));
const templateFile = path.join(currentDir, "templates", "unit.service");

console.log(templateFile); // 不受执行命令时所在目录影响

八、信号处理

长期运行的服务会收到系统信号。Docker、Kubernetes、systemd 停服务时常见的是 SIGTERM

js
process.on("SIGTERM", () => {
  console.log("received SIGTERM");

  // 这里放关闭数据库连接、停止接收请求、保存状态等清理逻辑
  process.exit(0);
});

脚本类程序通常不写信号处理。HTTP 服务、定时调度器、常驻 Worker 才需要补,尤其是容器里要处理 SIGTERM,否则发布时旧连接可能拖住退出。

九、一个标准脚本骨架

CLI 或巡检脚本可以先用这个骨架。

js
async function main() {
  const [, , service] = process.argv;

  if (!service) {
    throw new Error("usage: node check.js <service>");
  }

  console.log(`checking ${service}`);
}

main().catch((err) => {
  console.error(err.message); // 统一错误出口
  process.exit(1);            // 保证失败时退出码非 0
});