Go语言如何编写代码并执行系统命令以启动一个新的终端呢?

huangapple go评论65阅读模式
英文:

How does Go write code and execute system commands to start a new terminal as a result?

问题

当我在Go代码中执行普通命令时,例如ls -ls,它可以正常执行并返回结果:

// 错误处理被省略了,实际上我的代码中有错误处理

func main() {
	for {
		var stdout bytes.Buffer
		cmd := exec.Command("sh","-c","ls -ls")
		cmd.Stdout = &stdout
		cmd.Run()
		fmt.Println(stdout.String())
	}
}

执行结果如下:

[root@10-x-x-xx /root]# go run main.go
/usr/bin/sh -c ls -ls
stdout: total 8
4 -rw-r--r-- 1 root root 358 Aug 14 23:09 main.go
4 -rw-r--r-- 1 root root 167 Aug 14 16:07 temp.yaml

但是当我执行一个启动新终端的命令时(不确定我的理解是否正确),程序会正常返回,没有错误,没有输出,并且没有跳转到新终端。

例如:nsenter -n --target PID。这是一个进入网络命名空间的命令。通常,它会返回一个“#”并成功进入。

// 错误处理被省略了,实际上我的代码中有错误处理

func main() {
	for {
		var stdout bytes.Buffer
		cmd := exec.Command("sh","-c","nsenter -n --target 123")
		cmd.Stdout = &stdout
		cmd.Run()
		fmt.Println(stdout.String())
	}
}

程序在执行完成后退出,但实际上并没有进入网络命名空间。

但是当我使用Python时,就没有这个问题,它是可执行的。

import os
os.system("nsenter -n --target 24347")

或者另一个问题,如何在Go中编写代码,以便我可以执行此代码以ssh到其他主机,我觉得这似乎是同样的问题。

英文:

When I execute an ordinary command in Go code, for example ls -ls, it can be executed normally and return the result:

// Error handling is omitted, in fact there is error handling in my code

func main() {
	for {
		var stdout bytes.Buffer
		cmd := exec.Command("sh","-c","ls -ls")
		cmd.Stdout = &stdout
		cmd.Run()
		fmt.Println(stdout.String())
	}
}

The execution results are as follows:

[root@10-x-x-xx /root]# go run main.go
/usr/bin/sh -c ls -ls
stdout: total 8
4 -rw-r--r-- 1 root root 358 Aug 14 23:09 main.go
4 -rw-r--r-- 1 root root 167 Aug 14 16:07 temp.yaml

But when I execute a command to start a new terminal (not sure if my understanding is correct), the program returns normally with no errors, no output, and no jump to the new terminal.

For example: nsenter -n --target PID. This is a command to enter the network namespace. Normally, it will return to a "#" and enter successfully.

// Error handling is omitted, in fact there is error handling in my code

func main() {
	for {
		var stdout bytes.Buffer
		cmd := exec.Command("sh","-c","nsenter -n --target 123")
		cmd.Stdout = &stdout
		cmd.Run()
		fmt.Println(stdout.String())
	}
}

The program exits after the execution is complete, but does not actually enter the network namespace.

But when I use python, there is no such problem, it is executable.

import os
os.system("nsenter -n --target 24347")

Or another question, how to write code in Go, so that I can execute this code to ssh to other hosts, I feel that this seems to be the same problem.

答案1

得分: 2

你有几个问题。

首先,你试图生成一个交互式命令(nsenter默认会在目标命名空间中启动一个shell),但你同时将stdout连接到一个变量,这意味着即使它能工作,你也无法在命令退出之前看到任何输出,这可能不是你想要的。

其次,你没有将stdin连接到任何有用的内容,这意味着大多数交互式命令将简单地退出。

要在Go中启动一个交互式子命令,你可以编写如下代码:

package main

import (
	"os"
	"os/exec"
)

func main() {
	cmd := exec.Command("bash", "--norc")
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "PS1=example$ ")
	cmd.Run()
}

(这将启动一个带有提示符example$ bash shell)

关键在于我们将stdinstdoutstderr连接到父进程的相应描述符,这样我们就可以与子进程进行交互。

如果你不打算启动一个交互式shell,你现在的代码是可以工作的。也就是说,你可以这样写:

package main

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

func main() {
	var stdout bytes.Buffer
	cmd := exec.Command("sudo", "nsenter", "-n", "--target", "2399896", "ip", "addr")
	cmd.Stdout = &stdout
	cmd.Run()
	fmt.Println(stdout.String())
}

上面的代码完全正常,并显示在目标网络命名空间中运行ip addr的结果。

请注意,你可能更常使用exec模块的Output方法:

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	cmd := exec.Command("sudo", "nsenter", "-n", "--target", "2399896", "ip", "addr")
	stdout, err := cmd.Output()
	if err != nil {
		panic(err)
	}
	fmt.Println(string(stdout))
}
英文:

You've got a couple of problems there.

First, you're trying to spawn an interactive command (nsenter will by default start a shell in the target namespace), but you're also connecting stdout to a variable, which means even if it works you wouldn't be able to see any output until after the command exits, which is probably not what you want.

Secondly, you're not connecting stdin to anything useful, which means that most interactive commands will simply exit.

To start an interactive subcommand in Go, you would write something like this:

package main

import (
	"os"
	"os/exec"
)

func main() {
	cmd := exec.Command("bash", "--norc")
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Env = os.Environ()
	cmd.Env = append(cmd.Env, "PS1=example$ ")
	cmd.Run()
}

(This starts a bash shell with the prompt example$ )

The key here is that we're connecting stdin, stdout, and stderr
to the corresponding descriptors of the parent process, which allows
us to interact with the subprocess.

If you weren't trying to start an interactive shell, what you have now
would work. That is, you could write:

package main

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

func main() {
	var stdout bytes.Buffer
	cmd := exec.Command("sudo", "nsenter", "-n", "--target", "2399896", "ip", "addr")
	cmd.Stdout = &stdout
	cmd.Run()
	fmt.Println(stdout.String())
}

The above works just fine and displays the results of running ip
addr
inside the target network namespace.

Note that you might more typically using the Output method from the
execmodule:

package main

import (
	"fmt"
	"os/exec"
)

func main() {
	cmd := exec.Command("sudo", "nsenter", "-n", "--target", "2399896", "ip", "addr")
	stdout, err := cmd.Output()
	if err != nil {
		panic(err)
	}
	fmt.Println(string(stdout))
}

huangapple
  • 本文由 发表于 2021年8月14日 23:18:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/68784613.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定