在终端中的 Go 应用程序中使用多行输入。

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

multiline input in a terminal Go application

问题

我需要让用户在控制台输入多行文本。

以下是我的代码:

package main

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

func main() {
	for {
		fmt.Println("如何在此处读取所有行?")
		in := bufio.NewReader(os.Stdin)
		result, err := in.ReadString('\n')
		if err != nil {
			fmt.Println(err)
		}
		fmt.Println("\nresult")
		fmt.Println(result)

	}
}

我在控制台中粘贴了:

Hello
World

它的输出是:

如何在此处读取所有行?

		Hello
		World


result


如何在此处读取所有行?

result
		Hello

如何在此处读取所有行?

result
		World

如何在此处读取所有行?

result


如何在此处读取所有行?


但我希望输出是:

如何在此处读取所有行?

	Hello
	World

result

如何在此处读取所有行?

result
Hello
World

如何在此处读取所有行?


我猜我需要使用`EOF`而不是`'\n'`。但具体如何操作呢?

# 更新

peterSo的答案有效,除非我尝试从剪贴板粘贴包含一个或多个空行的文本,例如:

Hello

World


它会打印:

输入行:
Hello

World结果:
Hello

输入行:


# 更新2
伟大的更新后的peterSO的答案现在可以处理包含空行的文本。

<details>
<summary>英文:</summary>

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

Here is my code:

    package main
    
    import (
    	&quot;bufio&quot;
    	&quot;fmt&quot;
    	&quot;os&quot;
    )
    
    func main() {
    	for {
    		fmt.Println(&quot;How to read all lines here?&quot;)
    		in := bufio.NewReader(os.Stdin)
    		result, err := in.ReadString(&#39;\n&#39;)
    		if err != nil {
    			fmt.Println(err)
    		}
    		fmt.Println(&quot;\nresult&quot;)
    		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 `&#39;\n&#39;`
 But how to do it exactly?

#Update

peterSo&#39;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&#39;s answer now works even for text with empty lines.

</details>


# 答案1
**得分**: 8

缓冲一组行并检测一组行的结束。例如,

    package main
    
    import (
    	"bufio"
    	"fmt"
    	"os"
    )
    
    func main() {
    	scn := bufio.NewScanner(os.Stdin)
    	for {
    		fmt.Println("输入行:")
    		var lines []string
    		for scn.Scan() {
    			line := scn.Text()
    			if len(line) == 1 {
    				// 分组分隔符(GS ^]):ctrl-]
    				if line[0] == '\x1D' {
    					break
    				}
    			}
    			lines = append(lines, line)
    		}
    
    		if len(lines) > 0 {
    			fmt.Println()
    			fmt.Println("结果:")
    			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
    		}
    	}
    }

控制台:

<pre>
输入行:
Hello
World
^]

结果:
Hello
World

输入行:
Farewell

World

^]

结果:
Farewell

World


输入行:
^]
</pre>

要终止一组行,请在空行上输入:`ctrl+`][Enter]。要终止输入,请输入一行:`ctrl+`][Enter]。

<details>
<summary>英文:</summary>

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

    package main
    
    import (
    	&quot;bufio&quot;
    	&quot;fmt&quot;
    	&quot;os&quot;
    )
    
    func main() {
    	scn := bufio.NewScanner(os.Stdin)
    	for {
    		fmt.Println(&quot;Enter Lines:&quot;)
    		var lines []string
    		for scn.Scan() {
    			line := scn.Text()
    			if len(line) == 1 {
    				// Group Separator (GS ^]): ctrl-]
    				if line[0] == &#39;\x1D&#39; {
    					break
    				}
    			}
    			lines = append(lines, line)
    		}
    
    		if len(lines) &gt; 0 {
    			fmt.Println()
    			fmt.Println(&quot;Result:&quot;)
    			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:

&lt;pre&gt;
Enter Lines:
Hello
World
^]

Result:
Hello
World

Enter Lines:
Farewell

World

^]

Result:
Farewell

World


Enter Lines:
^]
&lt;/pre&gt;

To terminate a set of lines, on an empty line, enter: &lt;`ctrl+`]&gt;&lt;`Enter`&gt;. To terminate input, enter a single line: &lt;`ctrl+`]&gt;&lt;`Enter`&gt;.

</details>



# 答案2
**得分**: 1

请查看[bufio.ReadString][1]的文档,它指出你传递给它的字符将导致函数停止读取并返回。如果你想接受多行输入,你需要想出一些方案来区分继续语句的换行符('\n')和终止语句的字符。

**编辑:**下面是一个简单的示例,通过扩展你的代码来识别多行输入。它通过将语句的“终止”定义为空字符串(只包含“\n”)来实现。你可以看到它只是将多个读取缓冲到一个“块”中。你可以更聪明地直接读取键盘,但无论如何,你都必须定义一些方案,以便在程序中可以识别多行和单行。

```go
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
		}
	}
}
英文:

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 (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;os&quot;
)

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

		if err != nil {
			fmt.Println(err)
		}
		if result[0] == &#39;\n&#39; {
			cnt = false
			fmt.Printf(&quot;&lt;----\n%s----&gt;\n&quot;, string(txt))
			txt = txt[:0]
		} else {
			txt = append(txt, result...)
			cnt = true
		}
	}
}

答案3

得分: 0

EOF无法帮助你,因为io.EOF的类型是'error',但in.ReadString期望的是一个字节(序列'\n'是一个字节)。但无论如何,如果我理解你的目标,你需要一种停止从stdin读取的方法。例如,一个特定的字符串。就像mysql客户端期望"exit;"字符串来结束你的会话。

看一下扫描行的示例http://golang.org/pkg/bufio/#example_Scanner_lines,并改进它,将

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

替换为类似以下的内容:

allInput += scanner.Text() + "\n"
if scanner.Text() == "exit;" {
    //退出时的操作...
    //...    
    fmt.Println("Exiting. Bye")
    return
}

或者,如果你希望你的程序在管道中工作,并从其他程序的输出中接收多行输入,你可以使用io.ReadFullPipeReader函数。

英文:

EOF can not help you, because io.EOF has type of 'error', but in.ReadString expects a byte (the sequence &#39;\n&#39; - 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 &#39;\n&#39;

with something like:

allInput += scanner.Text() + &quot;\n&quot;
if scanner.Text() == &quot;exit;&quot; {
    //your actions on exit...
    //...    
    fmt.Println(&quot;Exiting. By&quot;)
    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

huangapple
  • 本文由 发表于 2015年6月14日 12:40:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/30825996.html
匿名

发表评论

匿名网友

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

确定