类似于getchar的函数

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

function similar to getchar

问题

有没有类似于C语言中的getchar函数的Go函数可以处理控制台中的Tab键按下?我想在我的控制台应用程序中实现一些自动补全的功能。

英文:

Is there a Go function similar to C's getchar able to handle tab press in console? I want to make some sort of completion in my console app.

答案1

得分: 25

C的getchar()示例:

  1. #include <stdio.h>
  2. void main()
  3. {
  4. char ch;
  5. ch = getchar();
  6. printf("输入的字符是:%c",ch);
  7. }

Go的等效代码:

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. )
  7. func main() {
  8. reader := bufio.NewReader(os.Stdin)
  9. input, _ := reader.ReadString('\n')
  10. fmt.Printf("输入的字符是:%v", string([]byte(input)[0]))
  11. // fmt.Printf("你输入的是:%v", []byte(input))
  12. }

最后被注释的那行代码只是展示了当你按下tab键时,第一个元素是U+0009('CHARACTER TABULATION')。

然而,对于你的需求(检测tab键),C的getchar()并不适用,因为它需要用户按下回车键。你需要的是像@miku提到的ncurses的getch()/ readline/ jLine这样的东西。使用这些工具,你实际上等待一个单个的按键。

所以你有多种选择:

  1. 使用ncurses / readline绑定,例如https://code.google.com/p/goncurses/或类似的https://github.com/nsf/termbox

  2. 自己编写,参考http://play.golang.org/p/plwBIIYiqG

  3. 使用os.Exec运行stty或jLine。

参考资料:

https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/zhBE5MH4n-Q

https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/S9AO_kHktiY

https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/icMfYF8wJCk

英文:

C's getchar() example:

  1. #include &lt;stdio.h&gt;
  2. void main()
  3. {
  4. char ch;
  5. ch = getchar();
  6. printf(&quot;Input Char Is :%c&quot;,ch);
  7. }

Go equivalent:

  1. package main
  2. import (
  3. &quot;bufio&quot;
  4. &quot;fmt&quot;
  5. &quot;os&quot;
  6. )
  7. func main() {
  8. reader := bufio.NewReader(os.Stdin)
  9. input, _ := reader.ReadString(&#39;\n&#39;)
  10. fmt.Printf(&quot;Input Char Is : %v&quot;, string([]byte(input)[0]))
  11. // fmt.Printf(&quot;You entered: %v&quot;, []byte(input))
  12. }

The last commented line just shows that when you press tab the first element is U+0009 ('CHARACTER TABULATION').

However for your needs (detecting tab) C's getchar() is not suitable as it requires the user to hit enter. What you need is something like ncurses' getch()/ readline/ jLine as mentioned by @miku. With these, you actually wait for a single keystroke.

So you have multiple options:

  1. Use ncurses / readline binding, for example https://code.google.com/p/goncurses/ or equivalent like https://github.com/nsf/termbox

  2. Roll your own see http://play.golang.org/p/plwBIIYiqG for starting point

  3. use os.Exec to run stty or jLine.

refs:

https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/zhBE5MH4n-Q

https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/S9AO_kHktiY

https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/icMfYF8wJCk

答案2

得分: 17

假设您想要无缓冲输入(无需按回车键),在UNIX系统上可以使用以下代码实现:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "os/exec"
  6. )
  7. func main() {
  8. // 禁用输入缓冲
  9. exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
  10. // 不在屏幕上显示输入的字符
  11. exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
  12. // 退出时恢复回显状态
  13. defer exec.Command("stty", "-F", "/dev/tty", "echo").Run()
  14. var b []byte = make([]byte, 1)
  15. for {
  16. os.Stdin.Read(b)
  17. fmt.Println("我得到了字节", b, "("+string(b)+")")
  18. }
  19. }
英文:

Assuming that you want unbuffered input (without having to hit enter), this does the job on UNIX systems:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;os&quot;
  5. &quot;os/exec&quot;
  6. )
  7. func main() {
  8. // disable input buffering
  9. exec.Command(&quot;stty&quot;, &quot;-F&quot;, &quot;/dev/tty&quot;, &quot;cbreak&quot;, &quot;min&quot;, &quot;1&quot;).Run()
  10. // do not display entered characters on the screen
  11. exec.Command(&quot;stty&quot;, &quot;-F&quot;, &quot;/dev/tty&quot;, &quot;-echo&quot;).Run()
  12. // restore the echoing state when exiting
  13. defer exec.Command(&quot;stty&quot;, &quot;-F&quot;, &quot;/dev/tty&quot;, &quot;echo&quot;).Run()
  14. var b []byte = make([]byte, 1)
  15. for {
  16. os.Stdin.Read(b)
  17. fmt.Println(&quot;I got the byte&quot;, b, &quot;(&quot;+string(b)+&quot;)&quot;)
  18. }
  19. }

答案3

得分: 9

其他答案建议使用以下方法:

  • 使用cgo

    • 效率低下
    • "cgo不是Go"(https://dave.cheney.net/2016/01/18/cgo-is-not-go)
  • 使用os.Execstty

    • 不可移植
    • 效率低下
    • 容易出错
  • 使用使用/dev/tty的代码

    • 不可移植
  • 使用GNU readline包

    • 如果它是C readline的包装器或者使用上述技术之一实现的,则效率低下
    • 否则可以

然而,对于简单的情况,可以直接使用Go项目的子存储库中的包。

[编辑:之前的答案使用了golang.org/x/crypto/ssh/terminal包,但该包已被弃用;它已移至golang.org/x/term。代码/链接已相应更新。]

基本上,使用term.MakeRawterm.Restore将标准输入设置为原始模式(检查错误,例如如果stdin不是终端);然后可以直接从os.Stdin读取字节,或者更可能是通过bufio.Reader(以提高效率)。

例如,像这样的代码:

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "log"
  6. "os"
  7. "golang.org/x/term"
  8. )
  9. func main() {
  10. // fd 0 is stdin
  11. state, err := term.MakeRaw(0)
  12. if err != nil {
  13. log.Fatalln("setting stdin to raw:", err)
  14. }
  15. defer func() {
  16. if err := term.Restore(0, state); err != nil {
  17. log.Println("warning, failed to restore terminal:", err)
  18. }
  19. }()
  20. in := bufio.NewReader(os.Stdin)
  21. for {
  22. r, _, err := in.ReadRune()
  23. if err != nil {
  24. log.Println("stdin:", err)
  25. break
  26. }
  27. fmt.Printf("read rune %q\r\n", r)
  28. if r == 'q' {
  29. break
  30. }
  31. }
  32. }
英文:

Other answers here suggest such things as:

  • Using cgo

  • os.Exec of stty

    • not portable
    • inefficient
    • error prone
  • using code that uses /dev/tty

    • not portable
  • using a GNU readline package

    • inefficient if it's a wrapper to C readline or if implemented using one of the above techniques
    • otherwise okay

However, for the simple case this is easy just using a package from the Go Project's Sub-repositories.

[Edit: Previously this answer used the golang.org/x/crypto/ssh/terminal package which has since been deprecated; it was moved to golang.org/x/term. Code/links updated appropriately.]

Basically, use
term.MakeRaw and
term.Restore
to set standard input to raw mode (checking for errors, e.g. if stdin is not a terminal); then you can either read bytes directly from os.Stdin, or more likely, via a bufio.Reader (for better efficiency).

For example, something like this:

  1. package main
  2. import (
  3. &quot;bufio&quot;
  4. &quot;fmt&quot;
  5. &quot;log&quot;
  6. &quot;os&quot;
  7. &quot;golang.org/x/term&quot;
  8. )
  9. func main() {
  10. // fd 0 is stdin
  11. state, err := term.MakeRaw(0)
  12. if err != nil {
  13. log.Fatalln(&quot;setting stdin to raw:&quot;, err)
  14. }
  15. defer func() {
  16. if err := term.Restore(0, state); err != nil {
  17. log.Println(&quot;warning, failed to restore terminal:&quot;, err)
  18. }
  19. }()
  20. in := bufio.NewReader(os.Stdin)
  21. for {
  22. r, _, err := in.ReadRune()
  23. if err != nil {
  24. log.Println(&quot;stdin:&quot;, err)
  25. break
  26. }
  27. fmt.Printf(&quot;read rune %q\r\n&quot;, r)
  28. if r == &#39;q&#39; {
  29. break
  30. }
  31. }
  32. }

答案4

得分: 8

感谢Paul Rademacher - 这在Mac上有效:

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "github.com/pkg/term"
  6. )
  7. func getch() []byte {
  8. t, _ := term.Open("/dev/tty")
  9. term.RawMode(t)
  10. bytes := make([]byte, 3)
  11. numRead, err := t.Read(bytes)
  12. t.Restore()
  13. t.Close()
  14. if err != nil {
  15. return nil
  16. }
  17. return bytes[0:numRead]
  18. }
  19. func main() {
  20. for {
  21. c := getch()
  22. switch {
  23. case bytes.Equal(c, []byte{3}):
  24. return
  25. case bytes.Equal(c, []byte{27, 91, 68}): // left
  26. fmt.Println("LEFT pressed")
  27. default:
  28. fmt.Println("Unknown pressed", c)
  29. }
  30. }
  31. return
  32. }
英文:

Thanks goes to Paul Rademacher - this works (at least on Mac):

  1. package main
  2. import (
  3. &quot;bytes&quot;
  4. &quot;fmt&quot;
  5. &quot;github.com/pkg/term&quot;
  6. )
  7. func getch() []byte {
  8. t, _ := term.Open(&quot;/dev/tty&quot;)
  9. term.RawMode(t)
  10. bytes := make([]byte, 3)
  11. numRead, err := t.Read(bytes)
  12. t.Restore()
  13. t.Close()
  14. if err != nil {
  15. return nil
  16. }
  17. return bytes[0:numRead]
  18. }
  19. func main() {
  20. for {
  21. c := getch()
  22. switch {
  23. case bytes.Equal(c, []byte{3}):
  24. return
  25. case bytes.Equal(c, []byte{27, 91, 68}): // left
  26. fmt.Println(&quot;LEFT pressed&quot;)
  27. default:
  28. fmt.Println(&quot;Unknown pressed&quot;, c)
  29. }
  30. }
  31. return
  32. }

答案5

得分: 1

1- 你可以使用C.getch()

这在Windows命令行中有效,在不输入回车的情况下只读取一个字符:
(在shell(终端)中运行输出的二进制文件,而不是在管道或编辑器中运行。)

  1. package main
  2. //#include<conio.h>
  3. import "C"
  4. import "fmt"
  5. func main() {
  6. c := C.getch()
  7. fmt.Println(c)
  8. }

2- 对于Linux(在Ubuntu上测试过):

  1. package main
  2. /*
  3. #include <stdio.h>
  4. #include <unistd.h>
  5. #include <termios.h>
  6. char getch(){
  7. char ch = 0;
  8. struct termios old = {0};
  9. fflush(stdout);
  10. if( tcgetattr(0, &old) < 0 ) perror("tcsetattr()");
  11. old.c_lflag &= ~ICANON;
  12. old.c_lflag &= ~ECHO;
  13. old.c_cc[VMIN] = 1;
  14. old.c_cc[VTIME] = 0;
  15. if( tcsetattr(0, TCSANOW, &old) < 0 ) perror("tcsetattr ICANON");
  16. if( read(0, &ch,1) < 0 ) perror("read()");
  17. old.c_lflag |= ICANON;
  18. old.c_lflag |= ECHO;
  19. if(tcsetattr(0, TCSADRAIN, &old) < 0) perror("tcsetattr ~ICANON");
  20. return ch;
  21. }
  22. */
  23. import "C"
  24. import "fmt"
  25. func main() {
  26. fmt.Println(C.getch())
  27. fmt.Println()
  28. }

参考:
https://stackoverflow.com/questions/7469139/what-is-equivalent-to-getch-getche-in-linux
https://stackoverflow.com/questions/8792317/why-cant-i-find-conio-h-on-linux


3- 这种方法也可以,但需要“Enter”:

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "os"
  6. )
  7. func main() {
  8. r := bufio.NewReader(os.Stdin)
  9. c, err := r.ReadByte()
  10. if err != nil {
  11. panic(err)
  12. }
  13. fmt.Println(c)
  14. }
英文:

1- You may use C.getch():

This works in Windows command line, Reads only one character without Enter:
(Run output binary file inside shell (terminal), not inside pipe or Editor.)

<!-- language: lang-golang -->

  1. package main
  2. //#include&lt;conio.h&gt;
  3. import &quot;C&quot;
  4. import &quot;fmt&quot;
  5. func main() {
  6. c := C.getch()
  7. fmt.Println(c)
  8. }

2- For Linux ( tested on Ubuntu ):

  1. package main
  2. /*
  3. #include &lt;stdio.h&gt;
  4. #include &lt;unistd.h&gt;
  5. #include &lt;termios.h&gt;
  6. char getch(){
  7. char ch = 0;
  8. struct termios old = {0};
  9. fflush(stdout);
  10. if( tcgetattr(0, &amp;old) &lt; 0 ) perror(&quot;tcsetattr()&quot;);
  11. old.c_lflag &amp;= ~ICANON;
  12. old.c_lflag &amp;= ~ECHO;
  13. old.c_cc[VMIN] = 1;
  14. old.c_cc[VTIME] = 0;
  15. if( tcsetattr(0, TCSANOW, &amp;old) &lt; 0 ) perror(&quot;tcsetattr ICANON&quot;);
  16. if( read(0, &amp;ch,1) &lt; 0 ) perror(&quot;read()&quot;);
  17. old.c_lflag |= ICANON;
  18. old.c_lflag |= ECHO;
  19. if(tcsetattr(0, TCSADRAIN, &amp;old) &lt; 0) perror(&quot;tcsetattr ~ICANON&quot;);
  20. return ch;
  21. }
  22. */
  23. import &quot;C&quot;
  24. import &quot;fmt&quot;
  25. func main() {
  26. fmt.Println(C.getch())
  27. fmt.Println()
  28. }

See:
https://stackoverflow.com/questions/7469139/what-is-equivalent-to-getch-getche-in-linux
https://stackoverflow.com/questions/8792317/why-cant-i-find-conio-h-on-linux


3- Also this works, but needs "Enter":

<!-- language: lang-golang -->

  1. package main
  2. import (
  3. &quot;bufio&quot;
  4. &quot;fmt&quot;
  5. &quot;os&quot;
  6. )
  7. func main() {
  8. r := bufio.NewReader(os.Stdin)
  9. c, err := r.ReadByte()
  10. if err != nil {
  11. panic(err)
  12. }
  13. fmt.Println(c)
  14. }

答案6

得分: -5

你也可以使用ReadRune:

  1. reader := bufio.NewReader(os.Stdin)
  2. // ...
  3. char, _, err := reader.ReadRune()
  4. if err != nil {
  5. fmt.Println("读取键盘输入错误...", err)
  6. }

Rune(符文)类似于字符,因为Go语言实际上没有字符,为了尝试支持多种语言/Unicode等。

英文:

You can also use ReadRune:

  1. reader := bufio.NewReader(os.Stdin)
  2. // ...
  3. char, _, err := reader.ReadRune()
  4. if err != nil {
  5. fmt.Println(&quot;Error reading key...&quot;, err)
  6. }

A rune is similar to a character, as GoLang does not really have characters, in order to try and support multiple languages/unicode/etc.

huangapple
  • 本文由 发表于 2012年12月31日 04:07:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/14094190.html
匿名

发表评论

匿名网友

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

确定