英文:
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)
关键在于我们将stdin、stdout和stderr连接到父进程的相应描述符,这样我们就可以与子进程进行交互。
如果你不打算启动一个交互式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
inside the target network namespace.
addr
Note that you might more typically using the Output
method from the
exec
module:
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))
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论