Golang不同版本中使用os/exec实现Top命令的结果不一致问题

Golang不同版本中使用os/exec实现Top命令的结果不一致问题

package main

import (
        "fmt"
        "os/exec"
        "strconv"
        "strings"
)

func  MyTop(topNum uint, args ...string) (out []byte, err error) {
        lineNumStr := strconv.FormatUint(uint64(topNum), 10)
        shCmd := []string{"top", "-n", "1", "-b"}
        shCmd = append(shCmd, args...)
        shCmd = append(shCmd, "|", "head", "-n", lineNumStr)

        cmd := exec.Command("bash", "-c", strings.Join(shCmd, " "))
        buf, err := cmd.Output()
        return buf, err
}

func main() {
   buf_mem, _ := MyTop(20, "-o", "%MEM")
   fmt.Printf("top memeory >\n %s", buf_mem)
    return
}

在 go1.15.7 和 go1.19.10 中的输出结果不同:

image

在 go1.19.10 中发生了什么?


更多关于Golang不同版本中使用os/exec实现Top命令的结果不一致问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

8 回复

我不认为这与命令配置选项有关。我在不同机器上运行的是相同的命令,即“top -n 1 -b -o %MEM”。

更多关于Golang不同版本中使用os/exec实现Top命令的结果不一致问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我终于发现是 $HOME/.toprc 配置文件的问题。我应该删除它。 感谢 @Dean_Davidson@dunric 😄

在其他环境中,top 命令似乎只是配置为关闭了 COMMAND 字段。请查阅 man 1 top 以了解配置选项,特别是个人配置文件部分。

嗯,我的 .bashrc 文件非常简单,里面没有什么特别的内容,都是默认设置。

你能在输出中指定列吗?

当然,我会尝试一下。

当我在两台主机(node1和node-77)的shell中直接运行 top -n 1 -b -o %MEM 命令时,结果是一致的。

我在一台安装了Go 1.19.10版本(node1)的主机上编译了一个二进制文件,然后在安装了Go 1.15.7版本的node-77主机上运行它,结果是它表现正常。

我怀疑 Go 版本与此问题无关。当你在第二个操作系统(node1 而不是 node-77)的 shell 中直接运行 top 命令时,会发生什么?你也可以通过在装有 Go 1.15.7 版本的系统上编译,然后在 node1 上使用该二进制文件来排除 Go 版本的影响,看看输出是否不同(我假设你在这里只是使用 go run .,而不是二进制文件)。

我在一台安装了Go 1.19.10的主机(节点1)上编译了一个二进制文件,然后在安装了Go 1.15.7的节点77上运行它,结果是它表现正常。

嗯,安装在节点77上的Go版本对二进制文件的运行方式没有影响,所以问题指向命令本身。检查一下你的 .bashrc 设置?我对 top 命令不是特别熟悉,否则我可能会提供更具体的故障排除步骤。你能指定一下输出中的列吗?

在Go 1.19.10中,os/exec包对命令执行的安全性进行了增强,特别是对shell命令注入的防护。您遇到的问题是由于exec.Command的参数处理方式发生了变化。

在Go 1.19中,当使用bash -c执行命令时,管道符号|和重定向符号等shell元字符不再被正确解析,除非它们作为单独的参数传递。以下是修复后的代码:

package main

import (
	"fmt"
	"os/exec"
	"strconv"
	"strings"
)

func MyTop(topNum uint, args ...string) (out []byte, err error) {
	lineNumStr := strconv.FormatUint(uint64(topNum), 10)
	
	// 构建完整的shell命令字符串
	shCmd := []string{"top", "-n", "1", "-b"}
	shCmd = append(shCmd, args...)
	shCmd = append(shCmd, "|", "head", "-n", lineNumStr)
	
	// 将整个命令作为单个字符串传递给bash -c
	cmd := exec.Command("bash", "-c", strings.Join(shCmd, " "))
	buf, err := cmd.Output()
	return buf, err
}

// 或者使用更安全的方式,避免shell管道
func MyTopSafe(topNum uint, args ...string) (out []byte, err error) {
	lineNumStr := strconv.FormatUint(uint64(topNum), 10)
	
	// 直接构建top命令,不使用shell管道
	topCmd := []string{"-n", "1", "-b"}
	topCmd = append(topCmd, args...)
	
	cmd := exec.Command("top", topCmd...)
	output, err := cmd.Output()
	if err != nil {
		return nil, err
	}
	
	// 在Go中处理输出,而不是通过shell管道
	lines := strings.Split(string(output), "\n")
	if len(lines) > int(topNum) {
		lines = lines[:topNum]
	}
	return []byte(strings.Join(lines, "\n")), nil
}

func main() {
	// 使用修复后的版本
	buf_mem, _ := MyTop(20, "-o", "%MEM")
	fmt.Printf("top memory >\n %s", buf_mem)
	
	// 或者使用更安全的版本
	buf_mem_safe, _ := MyTopSafe(20, "-o", "%MEM")
	fmt.Printf("top memory (safe) >\n %s", buf_mem_safe)
}

关键变化:

  1. 在Go 1.19中,exec.Command对参数的处理更加严格,shell元字符需要正确转义或作为单独参数传递
  2. 建议的解决方案是将整个命令作为单个字符串传递给bash -c
  3. 更安全的方法是在Go代码中处理输出,而不是依赖shell管道

如果您需要保持与旧版本相同的行为,使用strings.Join(shCmd, " ")将整个命令作为单个字符串传递即可。

回到顶部