Maxim Yefremov
Maxim Yefremov

Reputation: 14165

multiline input in a terminal Go application

I need to let a user input multiline text to the console.

Here is my code:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    for {
        fmt.Println("How to read all lines here?")
        in := bufio.NewReader(os.Stdin)
        result, err := in.ReadString('\n')
        if err != nil {
            fmt.Println(err)
        }
        fmt.Println("\nresult")
        fmt.Println(result)     

    }
}

I pasted in the console:

    Hello
    World

It outputs:

How to read all lines here?

        Hello
        World


result


How to read all lines here?

result
        Hello

How to read all lines here?

result
        World

How to read all lines here?

result


How to read all lines here?

But I expect it to be:

         How to read all lines here?

                Hello
                World


        result


        How to read all lines here?

        result
                Hello
                World

        How to read all lines here?

I guess I need to use something like EOF instead of '\n' But how to do it exactly?

Update

peterSo's answer works except in case when I am trying to paste from clipboard a text with one or more empty lines in-between, like:

Hello

World

It prints

Enter Lines:
Hello

WorldResult:
Hello

Enter Lines:

Update 2

The great updated peterSO's answer now works even for text with empty lines.

Upvotes: 3

Views: 7486

Answers (3)

peterSO
peterSO

Reputation: 166569

Buffer a set of lines and detect the end of a set of lines. For example,

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    scn := bufio.NewScanner(os.Stdin)
    for {
        fmt.Println("Enter Lines:")
        var lines []string
        for scn.Scan() {
            line := scn.Text()
            if len(line) == 1 {
                // Group Separator (GS ^]): ctrl-]
                if line[0] == '\x1D' {
                    break
                }
            }
            lines = append(lines, line)
        }

        if len(lines) > 0 {
            fmt.Println()
            fmt.Println("Result:")
            for _, line := range lines {
                fmt.Println(line)
            }
            fmt.Println()
        }

        if err := scn.Err(); err != nil {
            fmt.Fprintln(os.Stderr, err)
            break
        }
        if len(lines) == 0 {
            break
        }
    }
}

Console:

Enter Lines:
Hello
World
^]

Result:
Hello
World

Enter Lines:
Farewell

World

^]

Result:
Farewell

World


Enter Lines:
^]

To terminate a set of lines, on an empty line, enter: <ctrl+]><Enter>. To terminate input, enter a single line: <ctrl+]><Enter>.

Upvotes: 8

vladwd
vladwd

Reputation: 176

EOF can not help you, because io.EOF has type of 'error', but in.ReadString expects a byte (the sequence '\n' - is a byte). But anyway, if I understand your goals, you need some way to stop reading from stdin. For example, a specific string. Like mysql client expects "exit;" string to end your session.

Take a look at scanning lines example http://golang.org/pkg/bufio/#example_Scanner_lines and improve it replacing

fmt.Println(scanner.Text()) // Println will add back the final '\n'

with something like:

allInput += scanner.Text() + "\n"
if scanner.Text() == "exit;" {
    //your actions on exit...
    //...    
    fmt.Println("Exiting. By")
    return
}

Or if you want you program to work in pipes and take multiline input from other program output, you can use io.ReadFull or PipeReader functions

Upvotes: 0

Walter Delevich
Walter Delevich

Reputation: 427

Look at the documentation for bufio.ReadString, it states that the character you pass to it will cause the function to stop reading and return. If you want to accept multi-line input, you'll need to come up with some scheme to differentiate newlines ('\n') that continue a statement, and whatever terminates a statement.

edit: Here is a bad quick hacky example that extends your code to recognize multi-line input. It does this by defining the "termination" of a statement to be the empty string containing only "\n". You can see it's just buffering multiple reads into one "block". You can get more clever and read the keyboard directly, but regardless you'll have to define some scheme that you can programmatically recognize as being multi-line vs. single line.

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    cnt := false
    var txt []byte
    for {
        result := make([]byte, 1024)
        if cnt == false {
            fmt.Println("How to read all lines here?")
        }
        in := bufio.NewReader(os.Stdin)
        _, err := in.Read(result)

        if err != nil {
            fmt.Println(err)
        }
        if result[0] == '\n' {
            cnt = false
            fmt.Printf("<----\n%s---->\n", string(txt))
            txt = txt[:0]
        } else {
            txt = append(txt, result...)
            cnt = true
        }
    }
}

Upvotes: 1

Related Questions