Golang中exec.Command处理Windows查询命令的输出问题

Golang中exec.Command处理Windows查询命令的输出问题 你好!我是GO语言的新手,遇到了一个问题需要一些帮助。

我正在编写一个小工具(在Manjaro Linux上,GO版本:go1.17.6 linux/amd64),并将其编译为Windows可执行文件(W10, amd64)。 (我在Windows机器上没有管理员权限,因此无法在那里安装GO。) 这个工具应该循环启动Windows查询工具(query user /server:xxx.xxx.xxx.xxx)来查询一系列主机,并希望对结果进行进一步处理。 下面是一个简化版的代码,只包含了有问题的部分。

代码本身运行得很完美,如果我将“query”替换为一些Linux命令,它会在我的Linux开发机上为每次迭代打印正确的输出。我认为这并不完全是语言问题,而更像是GO与Windows交互的一个特定问题。 为了测试,我选择了3个IP地址,它们对应3种不同的“query”响应: (这些响应是在Windows命令提示符下启动query命令时得到的)

  1. 远程机器正在被使用:query的输出USER NAME
  2. 远程机器空闲:query的输出No user exists for
  3. 远程机器宕机:query的输出Error The RPC server is unavailable

当我运行代码时,循环末尾的fmt.print会为有用户登录的PC打印正确的用户名。但对于另外两种情况,它什么都不打印。字符串是空的。

我在Stackoverflow.com上问了这个问题,有一些关于管道的想法,但建议的解决方案都没有奏效。 我不明白,当我在命令行启动query和在我的代码中启动它时,区别在哪里。如果我将“query”替换为“ipconfig”并在Windows机器上运行,它就能很好地为每次迭代打印输出。 query做了什么不同的事情,导致我的代码中的StdOut只接收到三种可能答案中的一种?有人有想法吗?谢谢。

package main

import (
    "os/exec"
    "strings"
    "fmt"
    "syscall"
    "log"
)

var ip_addresses []string

func main() {
        fmt.Print("Starting... \n")
        ip_addresses = append(ip_addresses,"/server:192.168.10.1")
        ip_addresses = append(ip_addresses,"/server:192.168.10.3")
        ip_addresses = append(ip_addresses,"/server:192.168.10.4")
        

    for _, eachline := range ip_addresses {
        if strings.HasPrefix(eachline, "#") != true {
            fmt.Print("IP Address: ", eachline, "\n")
            cmd := exec.Command("query", "user", eachline)
            stdout, err := cmd.Output()
            if exit, ok := err.(*exec.ExitError); ok {
                if status, ok := exit.Sys().(syscall.WaitStatus); ok {
                    log.Printf("Exit Status: %d", status.ExitStatus())
                }
            } else {
                log.Fatal(err)
            }
            fmt.Print(string(stdout))
        }
    }
}

更多关于Golang中exec.Command处理Windows查询命令的输出问题的实战教程也可以访问 https://www.itying.com/category-94-b0.html

7 回复

感谢您的回复。 由于该项目已不再必要,我已将其放弃。

更多关于Golang中exec.Command处理Windows查询命令的输出问题的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


太棒了!我刚刚测试了,它运行得非常完美。谢谢你!

是的,(*exec.ExitError).Stderr 中包含以下信息:

Error 0x000006BA enumerating sessionnames
Error [1722]:The RPC server is unavailable.

谢谢肖恩!👍 如果用一个不存在的主机运行你的代码会发生什么?查询应该会显示如 #3 中的错误信息。你的代码会打印出那条信息吗?(我只能等到明天早上登录工作电脑时才能测试你的代码。)或者这段代码还不是那个变通方案…… 现在太晚了,我已经无法用Go语言思考了……😄

你是否尝试过捕获执行命令的标准输出和标准错误?有时,本应输出到标准错误的信息会与标准输出混在一起。这可能解释了为什么在某些情况下你无法获得预期的结果。

你提到,从你的代码中运行“查询”与在命令行中运行的结果不同。Windows命令在通过程序执行时有时会表现出不同的行为,这确实令人费解。说到探索,你是否考虑过查看廉价Windows密钥子版块上的有用提示?令人惊讶的是,这些提示有时能带来意想不到的解决方案。

我可以复现这个问题,并且我认为我有一个解决方法,但我不太明白其中的原因。以下是我编写的类似代码:

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	servers := []string{
		"localhost",
	}

	for _, server := range servers {
		cmd := exec.Command("query", "user", "/server:" + server)
		out, err := cmd.Output()
		fmt.Println(string(out))
		if err != nil {
			if ee, ok := err.(*exec.ExitError); ok {
				fmt.Println("ExitError:", string(ee.Stderr))
			} else {
				fmt.Println("err:", err)
			}
		}
	}
}

它产生以下输出:

C:\Users\Sean>go run Downloads\test.go
 USERNAME              SESSIONNAME        ID  STATE   IDLE TIME  LOGON TIME
 sean                  rdp-tcp#2           2  Active          .  1/25/2022 6:48 AM

ExitError:

这(我认为)告诉我 query user 命令成功获取了输出,但同时返回了一个非 nilerror

在Windows中,query命令的输出行为比较特殊:成功时输出到标准输出(stdout),错误时输出到标准错误(stderr)。你的代码只捕获了cmd.Output()返回的stdout,所以当命令返回错误时,stderr的内容没有被捕获。

以下是修改后的代码,同时捕获stdout和stderr:

package main

import (
    "bytes"
    "fmt"
    "log"
    "os/exec"
    "strings"
)

func main() {
    fmt.Print("Starting...\n")
    
    ip_addresses := []string{
        "/server:192.168.10.1",
        "/server:192.168.10.3",
        "/server:192.168.10.4",
    }

    for _, eachline := range ip_addresses {
        if strings.HasPrefix(eachline, "#") {
            continue
        }
        
        fmt.Printf("IP Address: %s\n", eachline)
        
        cmd := exec.Command("query", "user", eachline)
        
        var stdout, stderr bytes.Buffer
        cmd.Stdout = &stdout
        cmd.Stderr = &stderr
        
        err := cmd.Run()
        
        if err != nil {
            // 命令执行出错,输出stderr
            fmt.Printf("Error: %v\n", err)
            fmt.Printf("Stderr: %s\n", stderr.String())
        }
        
        // 输出stdout(无论成功与否)
        if stdout.Len() > 0 {
            fmt.Printf("Stdout: %s\n", stdout.String())
        }
        
        fmt.Println("---")
    }
}

或者使用CombinedOutput()获取合并的输出:

package main

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

func main() {
    fmt.Print("Starting...\n")
    
    ip_addresses := []string{
        "/server:192.168.10.1",
        "/server:192.168.10.3",
        "/server:192.168.10.4",
    }

    for _, eachline := range ip_addresses {
        if strings.HasPrefix(eachline, "#") {
            continue
        }
        
        fmt.Printf("IP Address: %s\n", eachline)
        
        cmd := exec.Command("query", "user", eachline)
        output, err := cmd.CombinedOutput()
        
        if err != nil {
            log.Printf("Command error: %v", err)
        }
        
        fmt.Printf("Output: %s\n", string(output))
        fmt.Println("---")
    }
}

对于Windows的query命令,还需要注意它可能返回非零的退出码。第一个示例分别处理stdout和stderr,更适合需要区分正常输出和错误信息的场景。第二个示例使用CombinedOutput()更简单,但会将stdout和stderr混合在一起。

回到顶部