
本文介绍如何使用 Go 语言执行外部命令并从其标准输出中提取结构化信息,重点演示利用正则表达式精准匹配并解析 ping 命令输出中的“Average = Xms”等关键指标,并安全转换为 time.Duration 类型供后续逻辑使用。
本文介绍如何使用 go 语言执行外部命令并从其标准输出中提取结构化信息,重点演示利用正则表达式精准匹配并解析 `ping` 命令输出中的“average = xms”等关键指标,并安全转换为 `time.duration` 类型供后续逻辑使用。
在 Go 中调用系统命令(如 ping)并解析其输出是运维工具、监控脚本和自动化任务中的常见需求。核心在于:捕获 stdout → 文本处理 → 提取目标字段 → 类型安全转换。以下是一个完整、健壮的实现方案。
✅ 正确执行命令并捕获输出
首先需修正原代码中的语法错误(如未声明变量类型、缺少 :=),并确保跨平台兼容性。Windows 和 Linux 的 ping 输出格式不同,因此需分别处理:
-
Windows:输出含
Average = 12ms -
Linux/macOS:输出含
min/avg/max/mdev = .../12.345/... ms
package main
import (
"bytes"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
"time"
)
type PingResult struct {
Average time.Duration `json:"average"`
}
// parsePingOutput 解析 ping 输出,支持 Windows 和 Linux 格式
func parsePingOutput(output []byte) (*PingResult, error) {
s := string(output)
result := &PingResult{}
// 匹配 Windows 格式:Average = 12ms
reWin := regexp.MustCompile(`Average = (\d+)ms`)
matchesWin := reWin.FindStringSubmatchIndex([]byte(s))
if len(matchesWin) > 0 {
msStr := string(output[matchesWin[0][2]:matchesWin[0][3]])
if ms, err := strconv.ParseInt(msStr, 10, 64); err == nil {
result.Average = time.Duration(ms) * time.Millisecond
return result, nil
}
}
// 匹配 Linux 格式:min/avg/max/mdev = 11.2/12.3/14.1/1.0 ms
reUnix := regexp.MustCompile(`min/avg/max/mdev = [\d.]+/([\d.]+)/[\d.]+/[\d.]+ ms`)
matchesUnix := reUnix.FindStringSubmatchIndex([]byte(s))
if len(matchesUnix) > 0 {
avgStr := string(output[matchesUnix[0][2]:matchesUnix[0][3]])
if avgFloat, err := strconv.ParseFloat(avgStr, 64); err == nil {
result.Average = time.Duration(avgFloat*1000) * time.Microsecond // 精确到微秒
return result, nil
}
}
return nil, fmt.Errorf("failed to parse average RTT from ping output")
}
func main() {
// 跨平台 ping 命令适配
var cmd *exec.Cmd
switch os := strings.ToLower(getOS()); os {
case "windows":
cmd = exec.Command("ping", "-n", "4", "8.8.8.8") // Windows: -n 指定次数
default:
cmd = exec.Command("ping", "-c", "4", "8.8.8.8") // Unix: -c
}
var outBuf bytes.Buffer
cmd.Stdout = &outBuf
cmd.Stderr = &outBuf // 同时捕获 stderr,避免因权限/网络问题静默失败
fmt.Printf("==> Executing: %s\n", strings.Join(cmd.Args, " "))
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "==> Command failed: %v\nOutput:\n%s\n", err, outBuf.String())
return
}
output := outBuf.Bytes()
fmt.Printf("==> Raw Output:\n%s\n", string(output))
pingRes, err := parsePingOutput(output)
if err != nil {
fmt.Printf("==> Parse Error: %v\n", err)
return
}
fmt.Printf("✅ Average RTT: %v (%s)\n", pingRes.Average, pingRes.Average.String())
}⚠️ 关键注意事项
-
错误处理不可省略:
cmd.Run()失败时(如目标不可达、无网络权限),Stdout可能为空或含错误信息,务必同时重定向Stderr并检查返回值。 -
正则匹配需严格:使用
FindStringSubmatchIndex替代FindStringSubmatch可避免字符串拷贝,提升效率;同时注意(\d+)ms中的括号必须正确捕获数字部分。 -
类型转换要安全:
strconv.ParseInt/ParseFloat必须检查错误,避免 panic;time.ParseDuration("12ms")虽简洁,但对非整数毫秒(如12.3ms)会失败,故推荐手动计算。 -
平台差异优先识别:通过
runtime.GOOS或轻量级 OS 探测(如getOS()辅助函数)选择对应正则与参数,比硬编码更可靠。
✅ 总结
Go 的 os/exec + regexp 组合提供了强大且可控的命令行输出解析能力。掌握 bytes.Buffer 捕获、多模式正则匹配、数值安全转换三步法,即可稳定提取任意 CLI 工具的关键指标。后续可进一步封装为通用解析器,支持 JSON 输出(如 curl -s ifconfig.me | jq '.ip')或流式处理大日志文件。

















