【问题标题】:How to read terminal's input buffer immediately after keypress如何在按键后立即读取终端的输入缓冲区
【发布时间】:2015-11-04 23:32:46
【问题描述】:

我想在我的 c 程序中读取箭头按键并用其他字符串替换它们(立即在终端本身中)。我正在尝试像在 unix 终端中一样实现 bash 历史记录功能。我写了这段代码。

int
main(int argc, char *argv[])
{
    char c;
    char str[1024];
    int i = 0;
    while((c = fgetc(stdin)) != '\n'){
      if(((int)c) == 27){
        c=fgetc(stdin);
        c=fgetc(stdin);
        if (c == 'A')
        {
          printf("%c[A%c[2K",27, 27);
          printf("UP");
        }
      }
      str[i++] = c;
    }
    printf("\n");

  return 0;
}

但是,这不起作用,因为终端等待换行符或 EOF 将输入缓冲区发送到标准输入。所以,我必须按回车键来分析用户输入。

在此answer 中,用户提到使用system ("/bin/stty raw");,但这会替换所有默认终端行为(例如退格、删除等)。

那么,如果检测到箭头按键,有什么方法可以直接读取/操作终端输入缓冲区并调整缓冲区本身?

环境 - Ubuntu (Linux)

更新 1: 有没有办法改变信号/中断(默认是按回车键),使终端将存储的输入发送到缓冲区?这也可以帮助我实现相同的行为。

最终代码:

我通过检查strace bash的输出发现了特定按键的ASCII字符

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>

#define ESCAPE '\33'
#define BACKSPACE '\177'
#define DELETE '~'

int main(){
    struct termios termios_p;
    struct termios termios_save;
    tcgetattr(0, &termios_p);
    termios_save = termios_p;

    termios_p.c_lflag &= ~(ICANON|ECHO);
    tcsetattr(0,TCSANOW, &termios_p);
    char buff;
    while(read(0, &buff, 1) >= 0){
        if(buff > 0){
            if(buff == ESCAPE){
                read(0, &buff, 1);
                read(0, &buff, 1);
                if(buff == 'A'){
                    write(2, "up", 2);
                }else if(buff == 'B'){
                    write(2, "down", 4);

                }else if(buff == 'C'){
                    write(2, "\33[C", 3);

                }else if(buff == 'D'){
                    write(2, "\10", 2);                    
                }
            }else if(buff == BACKSPACE){
                write(2, "\10\33[1P", 5);
            }else if(buff == DELETE){
                write(2, "\33[1P", 4);
            }else{
                write(2,&buff,1);
            }
            // write(2,&buff,1);
            if(buff == 'q'){
                break;
            }
        }
    }
    tcsetattr(0,TCSANOW, &termios_save);
    return 0;
}

【问题讨论】:

  • 您应该添加“linux”作为标签,因为这取决于系统,并且从您的“/bin/stty raw”(和统计信息)我假设您在 Linux 上?
  • @ThomasPadron-McCarthy 是的,我在 ubuntu 中
  • 为什么不直接使用 readline 库?

标签: c linux stdin input-buffer


【解决方案1】:

您似乎正在寻找类似的东西。

程序本质上是等待用户输入。如果按下向上箭头键,程序会打印“按下箭头键”然后退出。如果按下了其他任何东西,它会等待用户完成他正在输入的内容并打印出来,然后退出。

#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
  struct termios oldt, newt;
  char ch, command[20];
  int oldf;

  tcgetattr(STDIN_FILENO, &oldt);
  newt = oldt;
  newt.c_lflag &= ~(ICANON | ECHO);
  tcsetattr(STDIN_FILENO, TCSANOW, &newt);
  oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
  fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

  while(1)
  {
    ch = getchar();
    if (ch == '\033')
    { printf("Arrow key\n"); ch=-1; break;}
    else if(ch == -1) // by default the function returns -1, as it is non blocking
    {
      continue;
    }
    else
    {
      break;
    }

  }
  tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
  fcntl(STDIN_FILENO, F_SETFL, oldf);

   if(ch != EOF)
   {
      ungetc(ch,stdin);ith
      putchar(ch);
      scanf("%s",command);
      printf("\n%s\n",command);

      return 1;
    }

    return 0;
  }

【讨论】:

  • @bishal - 您可能需要在调用 tcgetattr 后微调标志以根据您的要求获取它。在这里您可以看到 getchar 已变为非阻塞。这就是为什么我需要为 -1 设置条件
  • 谢谢,这是我正在寻找的代码。我必须做更多的调整才能获得类似终端的行为。我已经添加了上面的代码。
  • ?!一个名为 newt 的变量
【解决方案2】:

在我的 Linux 机器上,我使用 ncurses:

#include<stdio.h>
#include<ncurses.h>

void checkArrow(){
    int ch;

    initscr();
    raw();
    keypad(stdscr, TRUE);
    noecho();

    printw("Press q to Exit\t");

    do{
        switch(ch){
            case KEY_UP: {
                printw("\n\nUp Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
            case KEY_DOWN:{
                printw("\n\nDown Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
            case KEY_LEFT:{
                printw("\n\nLeft Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
            case KEY_RIGHT:{
                printw("\n\nRight Arrow\n");
                printw("Press q to Exit\t");
                break;
            }
        }
    }while((ch = getch()) != 'q');

    printw("\n\n\t\t\t-=GoodBye=-!\n");
    refresh();
    endwin();
}

int main(){
    checkArrow();

    return 0;
}
Up Arrow
Press q to Exit 
Down Arrow
Press q to Exit 

Left Arrow
Press q to Exit 

Right Arrow
Press q to Exit 

【讨论】:

  • 您的代码有效...但是请阅读我的问题,我说“保留默认终端行为”,我需要它像普通终端一样工作。您的代码行为与使用 system ("/bin/stty raw"); 非常相似
猜你喜欢
  • 2017-10-21
  • 2013-07-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-20
相关资源
最近更新 更多