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命令时得到的)
- 远程机器正在被使用:query的输出是 USER NAME
- 远程机器空闲:query的输出是 No user exists for
- 远程机器宕机: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
感谢您的回复。 由于该项目已不再必要,我已将其放弃。
更多关于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 命令成功获取了输出,但同时返回了一个非 nil 的 error。
在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混合在一起。

