Home > other >  How to interact with another console application using golang?
How to interact with another console application using golang?

Time:01-17

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.

  •  Tags:  
  • Related