You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

优化SSH配置文件转JSON格式的JavaScript代码

SSH配置转JSON代码优化

需求说明

需要将本地~/.ssh/config配置文件(含注释)转换为指定结构的JSON,要求移除所有#开头的注释行,每行首元素作为键、后续内容作为对应值。现有代码存在两个问题:

  • reduce循环无法自动处理最后一个Host条目,需要额外手动添加
  • 未考虑值中包含空格的配置项(如部分特殊路径或ForwardAgent这类带参数的项)

原配置文件

# Personal account - default config
Host github.com-user1
   HostName github.com
   User git
   IdentityFile ~/.ssh/id_rsa_user1
# Official account
Host github.com-user2
   HostName github.com
   User git
   IdentityFile ~/.ssh/id_rsa_user2

目标JSON结构

[
  {
    "Host": "github.com-user1",
    "HostName": "github.com",
    "User": "git",
    "IdentityFile": "~/.ssh/id_rsa_user1"
  },
  {
    "Host": "github.com-user2",
    "HostName": "github.com",
    "User": "git",
    "IdentityFile": "~/.ssh/id_rsa_user2"
  }
]

原代码

import { exec as cExec } from "child_process";
import { promisify } from "util";

const exec = promisify(cExec);

async function ssh() {
  const cmd = 'while IFS= read -r line; do echo "$line"; done < "$HOME/.ssh/config"'
  const { stdout, stderr } = await exec(cmd);
  if (stderr) throw new Error(stderr);

  const a = stdout.replace("\n", "");
  let obj = {};
  const b = stdout.trim().split("\n").reduce((r, a) => {
    if (!a.includes("#")) {
      let [name, value] = a.trim().split(" ");

      if (name === "Host") {
        if (Object.keys(obj).length > 0) r.push(obj);
        obj = {};
        obj[name] = value;
      } else {
        obj[name] = value;
      }

    }

    return r;
  }, []);

  if (Object.keys(obj).length > 0) b.push(obj);
  return b
}

优化后的代码

import { readFile } from "fs/promises";
import { homedir } from "os";

async function parseSshConfig() {
  // 直接读取ssh配置文件,替代shell exec方式
  const configPath = `${homedir()}/.ssh/config`;
  const content = await readFile(configPath, "utf8");

  return content.split("\n").reduce((acc, line) => {
    const trimmedLine = line.trim();
    // 跳过空行和注释行
    if (!trimmedLine || trimmedLine.startsWith("#")) return acc;

    // 用正则分割键和值,兼容值中包含空格的情况,只分割第一个空白符后的内容
    const [key, ...valueParts] = trimmedLine.split(/\s+/);
    const value = valueParts.join(" ");

    if (key === "Host") {
      // 如果当前已有未提交的配置项,先推入结果数组
      if (acc.currentHost && Object.keys(acc.currentHost).length > 0) {
        acc.result.push(acc.currentHost);
      }
      // 初始化新的Host配置
      acc.currentHost = { [key]: value };
    } else {
      // 追加其他配置项到当前Host
      acc.currentHost[key] = value;
    }

    return acc;
  }, { result: [], currentHost: {} })
  // 最后处理剩余的未提交配置项
  .then(acc => {
    if (acc.currentHost && Object.keys(acc.currentHost).length > 0) {
      acc.result.push(acc.currentHost);
    }
    return acc.result;
  });
}

优化点说明

  • 替换文件读取方式:用fs.promises.readFile直接读取文件,避免调用shell命令,更高效且安全
  • 移除外部变量依赖:将当前Host配置对象放到reduce的累积器中,使reduce逻辑更纯粹,避免外部状态污染
  • 兼容值含空格的配置:使用split(/\s+/)分割后再拼接,确保值中包含空格的配置项(如带空格的路径、ForwardAgent yes等)能正确解析
  • 自动处理最后一个Host:在reduce结束后的后续处理中统一检查并推送剩余的配置项,无需额外的判断代码
  • 代码结构更清晰:函数命名更具语义化,逻辑分层明确

内容的提问来源于stack exchange,提问作者Paul

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 03:00:06