如何使用golang与另一个控制台应用程序进行交互?

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

How to interact with another console application using golang?

问题

我想在自己的Go应用程序中与另一个控制台应用程序进行交互。我不想编写另一个FTP客户端,但我将以FTP应用程序作为示例,因为它应该在任何地方都可用。

请参考以下示例代码:

func main() {
    cmd := exec.Command("/bin/bash", "-c", "ftp")
    cmd.Stdin = os.Stdin
    cmd.Stdout = os.Stdout
    cmd.Stderr = os.Stderr

    cmd.Start()
    time.Sleep(2 * time.Second)
    io.WriteString(cmd.Stdout, "help\r\n")
    time.Sleep(2 * time.Second)
    io.WriteString(cmd.Stdout, "exit\r\n")
    cmd.Wait()
}

在"cmd.Start()"之后,我可以在控制台中看到"ftp>"。然后,我尝试将"help"写入ftp应用程序。我可以在文本中看到"help",但我没有得到预期的输出。当我使用键盘输入"help"时,我会收到预期的文本。

这个示例的预期工作流程如下:

ftp> help
Commands may be abbreviated.  Commands are:

!		dir		mdelete		qc		site
$		disconnect	mdir		sendport	size
account		exit		mget		put		status
append		form		mkdir		pwd		struct
ascii		get		mls		quit		system
bell		glob		mode		quote		sunique
binary		hash		modtime		recv		tenex
bye		help		mput		reget		tick
case		idle		newer		rstatus		trace
cd		image		nmap		rhelp		type
cdup		ipany		nlist		rename		user
chmod		ipv4		ntrans		reset		umask
close		ipv6		open		restart		verbose
cr		lcd		prompt		rmdir		?
delete		ls		passive		runique
debug		macdef		proxy		send
ftp> exit

有人有想法如何实现这个吗?这个可能吗?

提前感谢您的反馈。

英文:

I'd like to interact with another console application from my own go application. I don't want to write another FTP client, but I'll take the ftp application in this example as it should be available everywhere.

Take the following example:

func main() {
  cmd := exec.Command("/bin/bash", "-c", "ftp")
  cmd.Stdin = os.Stdin
  cmd.Stdout = os.Stdout
  cmd.Stderr = os.Stderr

  cmd.Start()
  time.Sleep(2 * time.Second)
  io.WriteString(cmd.Stdout, "help\r\n")
  time.Sleep(2 * time.Second)
  io.WriteString(cmd.Stdout, "exit\r\n")
  cmd.Wait()
}

After the "cmd.Start()" I can see "ftp>" in the console. Then I tried to write "help" into the ftp application. I can see "help" as text, but I do not get the expected output back. When I write "help" with the keyboard, I receive the expected text.

The expected workflow for this example would be the following:

ftp> help
Commands may be abbreviated.  Commands are:

!		dir		mdelete		qc		site
$		disconnect	mdir		sendport	size
account		exit		mget		put		status
append		form		mkdir		pwd		struct
ascii		get		mls		quit		system
bell		glob		mode		quote		sunique
binary		hash		modtime		recv		tenex
bye		help		mput		reget		tick
case		idle		newer		rstatus		trace
cd		image		nmap		rhelp		type
cdup		ipany		nlist		rename		user
chmod		ipv4		ntrans		reset		umask
close		ipv6		open		restart		verbose
cr		lcd		prompt		rmdir		?
delete		ls		passive		runique
debug		macdef		proxy		send
ftp> exit

Has anyone an idea how I can realize this? Is it possible?

Thanks in advance for your feedback.

答案1

得分: 1

感谢您的回答。我尝试使用您的建议进行改进,并使用管道进行了修改。

现在代码如下所示:

func main() {
    cmd := exec.Command("/bin/bash", "-c", "ftp")

    stdin, err := cmd.StdinPipe()
    if err != nil {
        log.Fatal(err)
    }
    stdout, err := cmd.StdoutPipe()
    if err != nil {
        log.Fatal(err)
    }
    stderr, err := cmd.StderrPipe()
    if err != nil {
        log.Fatal(err)
    }

    go func() {
        defer stdin.Close()

        stdinCommand := "status\n"
        fmt.Print(time.Now().UTC().Format("15:04:05") + " STDIN: " + stdinCommand)
        io.WriteString(stdin, stdinCommand)

        time.Sleep(1 * time.Second)

        stdinCommand = "help\n"
        fmt.Print(time.Now().UTC().Format("15:04:05") + " STDIN: " + stdinCommand)
        io.WriteString(stdin, stdinCommand)
        time.Sleep(2 * time.Second)
    }()

    scanner := bufio.NewScanner(stdout)
    go func() {
        for scanner.Scan() {
            fmt.Println(time.Now().UTC().Format("15:04:05") + " STDOUT: " + scanner.Text())
        }
    }()

    scannerError := bufio.NewScanner(stderr)
    go func() {
        for scannerError.Scan() {
            fmt.Println(time.Now().UTC().Format("15:04:05") + " STDERR: " + scanner.Text())
        }
    }()

    if err := cmd.Run(); err != nil {
        log.Fatal(err)
    }
    fmt.Print(time.Now().UTC().Format("15:04:05") + " done")
}

现在的输出如下所示:

15:24:26 STDIN: status
15:24:27 STDIN: help
15:24:29 STDOUT: Not connected.
15:24:29 STDOUT: No proxy connection.
15:24:29 STDOUT: Connecting using address family: any.
15:24:29 STDOUT: Mode: ; Type: ; Form: ; Structure: 
15:24:29 STDOUT: Verbose: off; Bell: off; Prompting: on; Globbing: on
15:24:29 STDOUT: Store unique: off; Receive unique: off
15:24:29 STDOUT: Case: off; CR stripping: on
15:24:29 STDOUT: Quote control characters: off
15:24:29 STDOUT: Ntrans: off
15:24:29 STDOUT: Nmap: off
15:24:29 STDOUT: Hash mark printing: off; Use of PORT cmds: on
15:24:29 STDOUT: Tick counter printing: off
15:24:29 STDOUT: Commands may be abbreviated.  Commands are:
15:24:29 STDOUT: 
15:24:29 STDOUT: !              dir             mdelete         qc              site
15:24:29 STDOUT: $              disconnect      mdir            sendport        size
15:24:29 STDOUT: account                exit            mget            put             status
15:24:29 STDOUT: append         form            mkdir           pwd             struct
15:24:29 STDOUT: ascii          get             mls             quit            system
15:24:29 STDOUT: bell           glob            mode            quote           sunique
15:24:29 STDOUT: binary         hash            modtime         recv            tenex
15:24:29 STDOUT: bye            help            mput            reget           tick
15:24:29 STDOUT: case           idle            newer           rstatus         trace
15:24:29 done

您是否有提示,我需要更改什么才能直接在输入后获取“status”的响应,然后发送“help”以获取帮助响应。最后,我想发送“exit”以退出程序。在当前版本中,只要我包含了“defer stdin.Close()”,它就会自动退出。当我删除它时,我就无法获得输出。

英文:

Thanks for your answers. I tried to improve it with your recommendations with piping.

Now it looks like this:

func main() {
cmd := exec.Command("/bin/bash", "-c", "ftp")
stdin, err := cmd.StdinPipe()
if err != nil {
log.Fatal(err)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
log.Fatal(err)
}
go func() {
defer stdin.Close()
stdinCommand := "status\n"
fmt.Print(time.Now().UTC().Format("15:04:05") + " STDIN: " + stdinCommand)
io.WriteString(stdin, stdinCommand)
time.Sleep(1 * time.Second)
stdinCommand = "help\n"
fmt.Print(time.Now().UTC().Format("15:04:05") + " STDIN: " + stdinCommand)
io.WriteString(stdin, stdinCommand)
time.Sleep(2 * time.Second)
}()
scanner := bufio.NewScanner(stdout)
go func() {
for scanner.Scan() {
fmt.Println(time.Now().UTC().Format("15:04:05") + " STDOUT: " + scanner.Text())
}
}()
scannerError := bufio.NewScanner(stderr)
go func() {
for scannerError.Scan() {
fmt.Println(time.Now().UTC().Format("15:04:05") + " STDERR: " + scanner.Text())
}
}()
if err := cmd.Run(); err != nil {
log.Fatal(err)
}
fmt.Print(time.Now().UTC().Format("15:04:05") + " done")
}

The output now looks like this:

15:24:26 STDIN: status
15:24:27 STDIN: help
15:24:29 STDOUT: Not connected.
15:24:29 STDOUT: No proxy connection.
15:24:29 STDOUT: Connecting using address family: any.
15:24:29 STDOUT: Mode: ; Type: ; Form: ; Structure: 
15:24:29 STDOUT: Verbose: off; Bell: off; Prompting: on; Globbing: on
15:24:29 STDOUT: Store unique: off; Receive unique: off
15:24:29 STDOUT: Case: off; CR stripping: on
15:24:29 STDOUT: Quote control characters: off
15:24:29 STDOUT: Ntrans: off
15:24:29 STDOUT: Nmap: off
15:24:29 STDOUT: Hash mark printing: off; Use of PORT cmds: on
15:24:29 STDOUT: Tick counter printing: off
15:24:29 STDOUT: Commands may be abbreviated.  Commands are:
15:24:29 STDOUT: 
15:24:29 STDOUT: !              dir             mdelete         qc              site
15:24:29 STDOUT: $              disconnect      mdir            sendport        size
15:24:29 STDOUT: account                exit            mget            put             status
15:24:29 STDOUT: append         form            mkdir           pwd             struct
15:24:29 STDOUT: ascii          get             mls             quit            system
15:24:29 STDOUT: bell           glob            mode            quote           sunique
15:24:29 STDOUT: binary         hash            modtime         recv            tenex
15:24:29 STDOUT: bye            help            mput            reget           tick
15:24:29 STDOUT: case           idle            newer           rstatus         trace
15:24:29 done

Do you have a hint, what I need to change to get the response for "status" directly behind the input and afterwards send "help" to get the help response. In the end I'd like to send "exit" to exit the program. With the current version it automatically exits as long as I have "defer stdin.Close()" included. When I remove it, I do not get the output.

答案2

得分: 0

你想在你的应用程序中使用管道。你尝试的方式是不行的。你在终端中输入命令的方式与Bash和操作系统处理它们的方式是不同的。

这个例子可能对你有帮助,它是从另一个SO问题中复制过来的(查看问题):

package main

import (
    "os"
    "os/exec"
)

func main() {
    c1 := exec.Command("ls")
    c2 := exec.Command("wc", "-l")
    c2.Stdin, _ = c1.StdoutPipe()
    c2.Stdout = os.Stdout
    _ = c2.Start()
    _ = c1.Run()
    _ = c2.Wait()
}
英文:

You want to have piping in your application. That doesn't happen in the way you are trying. The way you write commands in your terminal is different from the way Bash and then OS treats them.

This example might help you, copied from another SO question (view question):

package main
import (
"os"
"os/exec"
)
func main() {
c1 := exec.Command("ls")
c2 := exec.Command("wc", "-l")
c2.Stdin, _ = c1.StdoutPipe()
c2.Stdout = os.Stdout
_ = c2.Start()
_ = c1.Run()
_ = c2.Wait()
}

huangapple
  • 本文由 发表于 2022年1月16日 14:58:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/70727990.html
匿名

发表评论

匿名网友

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

确定