类似于getchar的函数

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

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()示例:

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

Go的等效代码:

package main

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

func main() {

    reader := bufio.NewReader(os.Stdin)
    input, _ := reader.ReadString('\n')

    fmt.Printf("输入的字符是:%v", string([]byte(input)[0]))

    // fmt.Printf("你输入的是:%v", []byte(input))
}

最后被注释的那行代码只是展示了当你按下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:

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

Go equivalent:

package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;os&quot;
)

func main() {

	reader := bufio.NewReader(os.Stdin)
	input, _ := reader.ReadString(&#39;\n&#39;)

	fmt.Printf(&quot;Input Char Is : %v&quot;, string([]byte(input)[0]))

	// fmt.Printf(&quot;You entered: %v&quot;, []byte(input))
}

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系统上可以使用以下代码实现:

package main

import (
    "fmt"
    "os"
    "os/exec"
)

func main() {
    // 禁用输入缓冲
    exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
    // 不在屏幕上显示输入的字符
    exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
    // 退出时恢复回显状态
    defer exec.Command("stty", "-F", "/dev/tty", "echo").Run()

    var b []byte = make([]byte, 1)
    for {
        os.Stdin.Read(b)
        fmt.Println("我得到了字节", b, "("+string(b)+")")
    }
}
英文:

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

package main

import (
    &quot;fmt&quot;
    &quot;os&quot;
    &quot;os/exec&quot;
)

func main() {
    // disable input buffering
    exec.Command(&quot;stty&quot;, &quot;-F&quot;, &quot;/dev/tty&quot;, &quot;cbreak&quot;, &quot;min&quot;, &quot;1&quot;).Run()
    // do not display entered characters on the screen
    exec.Command(&quot;stty&quot;, &quot;-F&quot;, &quot;/dev/tty&quot;, &quot;-echo&quot;).Run()
    // restore the echoing state when exiting
    defer exec.Command(&quot;stty&quot;, &quot;-F&quot;, &quot;/dev/tty&quot;, &quot;echo&quot;).Run()

    var b []byte = make([]byte, 1)
    for {
        os.Stdin.Read(b)
        fmt.Println(&quot;I got the byte&quot;, b, &quot;(&quot;+string(b)+&quot;)&quot;)
    }
}

答案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(以提高效率)。

例如,像这样的代码:

package main

import (
	"bufio"
	"fmt"
	"log"
	"os"

	"golang.org/x/term"
)

func main() {
	// fd 0 is stdin
	state, err := term.MakeRaw(0)
	if err != nil {
		log.Fatalln("setting stdin to raw:", err)
	}
	defer func() {
		if err := term.Restore(0, state); err != nil {
			log.Println("warning, failed to restore terminal:", err)
		}
	}()

	in := bufio.NewReader(os.Stdin)
	for {
		r, _, err := in.ReadRune()
		if err != nil {
			log.Println("stdin:", err)
			break
		}
		fmt.Printf("read rune %q\r\n", r)
		if r == 'q' {
			break
		}
	}
}
英文:

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:

package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;os&quot;

	&quot;golang.org/x/term&quot;
)

func main() {
	// fd 0 is stdin
	state, err := term.MakeRaw(0)
	if err != nil {
		log.Fatalln(&quot;setting stdin to raw:&quot;, err)
	}
	defer func() {
		if err := term.Restore(0, state); err != nil {
			log.Println(&quot;warning, failed to restore terminal:&quot;, err)
		}
	}()

	in := bufio.NewReader(os.Stdin)
	for {
		r, _, err := in.ReadRune()
		if err != nil {
			log.Println(&quot;stdin:&quot;, err)
			break
		}
		fmt.Printf(&quot;read rune %q\r\n&quot;, r)
		if r == &#39;q&#39; {
			break
		}
	}
}

答案4

得分: 8

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

package main

import (
	"bytes"
	"fmt"

	"github.com/pkg/term"
)

func getch() []byte {
	t, _ := term.Open("/dev/tty")
	term.RawMode(t)
	bytes := make([]byte, 3)
	numRead, err := t.Read(bytes)
	t.Restore()
	t.Close()
	if err != nil {
		return nil
	}
	return bytes[0:numRead]
}

func main() {
	for {
		c := getch()
		switch {
		case bytes.Equal(c, []byte{3}):
			return
		case bytes.Equal(c, []byte{27, 91, 68}): // left
			fmt.Println("LEFT pressed")
		default:
			fmt.Println("Unknown pressed", c)
		}
	}
	return
}
英文:

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

package main

import (
	&quot;bytes&quot;
	&quot;fmt&quot;

	&quot;github.com/pkg/term&quot;
)

func getch() []byte {
	t, _ := term.Open(&quot;/dev/tty&quot;)
	term.RawMode(t)
	bytes := make([]byte, 3)
	numRead, err := t.Read(bytes)
	t.Restore()
	t.Close()
	if err != nil {
		return nil
	}
	return bytes[0:numRead]
}

func main() {
	for {
		c := getch()
		switch {
		case bytes.Equal(c, []byte{3}):
			return
		case bytes.Equal(c, []byte{27, 91, 68}): // left
			fmt.Println(&quot;LEFT pressed&quot;)
		default:
			fmt.Println(&quot;Unknown pressed&quot;, c)
		}
	}
	return
}

答案5

得分: 1

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

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

package main

//#include<conio.h>
import "C"

import "fmt"

func main() {
    c := C.getch()
    fmt.Println(c)
}

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

package main

/*
#include <stdio.h>
#include <unistd.h>
#include <termios.h>
char getch(){
    char ch = 0;
    struct termios old = {0};
    fflush(stdout);
    if( tcgetattr(0, &old) < 0 ) perror("tcsetattr()");
    old.c_lflag &= ~ICANON;
    old.c_lflag &= ~ECHO;
    old.c_cc[VMIN] = 1;
    old.c_cc[VTIME] = 0;
    if( tcsetattr(0, TCSANOW, &old) < 0 ) perror("tcsetattr ICANON");
    if( read(0, &ch,1) < 0 ) perror("read()");
    old.c_lflag |= ICANON;
    old.c_lflag |= ECHO;
    if(tcsetattr(0, TCSADRAIN, &old) < 0) perror("tcsetattr ~ICANON");
    return ch;
}
*/
import "C"

import "fmt"

func main() {
    fmt.Println(C.getch())
    fmt.Println()
}

参考:
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”:

package main

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

func main() {
    r := bufio.NewReader(os.Stdin)
    c, err := r.ReadByte()
    if err != nil {
        panic(err)
    }
    fmt.Println(c)
}
英文:

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 -->

package main

//#include&lt;conio.h&gt;
import &quot;C&quot;

import &quot;fmt&quot;

func main() {
	c := C.getch()
	fmt.Println(c)
}

2- For Linux ( tested on Ubuntu ):

package main

/*
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;termios.h&gt;
char getch(){
	char ch = 0;
	struct termios old = {0};
	fflush(stdout);
	if( tcgetattr(0, &amp;old) &lt; 0 ) perror(&quot;tcsetattr()&quot;);
	old.c_lflag &amp;= ~ICANON;
	old.c_lflag &amp;= ~ECHO;
	old.c_cc[VMIN] = 1;
	old.c_cc[VTIME] = 0;
	if( tcsetattr(0, TCSANOW, &amp;old) &lt; 0 ) perror(&quot;tcsetattr ICANON&quot;);
	if( read(0, &amp;ch,1) &lt; 0 ) perror(&quot;read()&quot;);
	old.c_lflag |= ICANON;
	old.c_lflag |= ECHO;
	if(tcsetattr(0, TCSADRAIN, &amp;old) &lt; 0) perror(&quot;tcsetattr ~ICANON&quot;);
	return ch;
}
*/
import &quot;C&quot;

import &quot;fmt&quot;

func main() {
	fmt.Println(C.getch())
	fmt.Println()
}

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 -->

package main

import (
	&quot;bufio&quot;
	&quot;fmt&quot;
	&quot;os&quot;
)

func main() {
	r := bufio.NewReader(os.Stdin)
	c, err := r.ReadByte()
	if err != nil {
		panic(err)
	}
	fmt.Println(c)
}

答案6

得分: -5

你也可以使用ReadRune:

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

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

英文:

You can also use ReadRune:

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

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:

确定