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.
CodePudding user response:
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()
}
CodePudding user response:
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.