質問

ユーザーがキーを押すまでループするプログラムをC(Linux)で記述しようとしていますが、各ループを続行するためにキーを押す必要はありません。

これを行う簡単な方法はありますか? select()でできるかもしれないと思いますが、それは大変な作業のようです。

また、 ctrl - c キーを押して、非ブロッキングioではなくプログラムが閉じる前にクリーンアップを実行する方法はありますか?

役に立ちましたか?

解決

すでに述べたように、sigactionを使用してctrl-cをトラップするか、selectを使用して標準入力をトラップできます。

ただし、後者の方法では、行単位モードではなく文字単位モードになるようにTTYを設定する必要があることに注意してください。後者がデフォルトです-テキストを入力した場合、Enterキーを押すまで実行中のプログラムの標準入力に送信されません。

tcsetattr()関数を使用してICANONモードをオフにし、おそらくECHOも無効にする必要があります。メモリから、プログラムの終了時に端末をICANONモードに戻す必要もあります!

完全を期すために、Unix TTYをセットアップし、DOS <conio.h>関数kbhit()およびgetch():をエミュレートするコード(nb:エラーチェックなし!)を示します:

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <termios.h>

struct termios orig_termios;

void reset_terminal_mode()
{
    tcsetattr(0, TCSANOW, &orig_termios);
}

void set_conio_terminal_mode()
{
    struct termios new_termios;

    /* take two copies - one for now, one for later */
    tcgetattr(0, &orig_termios);
    memcpy(&new_termios, &orig_termios, sizeof(new_termios));

    /* register cleanup handler, and set the new terminal mode */
    atexit(reset_terminal_mode);
    cfmakeraw(&new_termios);
    tcsetattr(0, TCSANOW, &new_termios);
}

int kbhit()
{
    struct timeval tv = { 0L, 0L };
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(0, &fds);
    return select(1, &fds, NULL, NULL, &tv);
}

int getch()
{
    int r;
    unsigned char c;
    if ((r = read(0, &c, sizeof(c))) < 0) {
        return r;
    } else {
        return c;
    }
}

int main(int argc, char *argv[])
{
    set_conio_terminal_mode();

    while (!kbhit()) {
        /* do some work */
    }
    (void)getch(); /* consume the character */
}

他のヒント

select() is a bit too low-level for convenience. I suggest you use the ncurses library to put the terminal in cbreak mode and delay mode, then call getch(), which will return ERR if no character is ready:

WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);

At that point you can call getch without blocking.

On UNIX systems, you can use sigaction call to register a signal handler for SIGINT signal which represents the Control+C key sequence. The signal handler can set a flag which will be checked in the loop making it to break appropriately.

You probably want kbhit();

//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>

using namespace std;

int main()
{
    while(1)
    {
        if(kbhit())
        {
            break;
        }
    }
}

this may not work on all environments. A portable way would be to create a monitoring thread and set some flag on getch();

Another way to get non-blocking keyboard input is to open the device file and read it!

You have to know the device file you are looking for, one of /dev/input/event*. You can run cat /proc/bus/input/devices to find the device you want.

This code works for me (run as an administrator).

  #include <stdlib.h>
  #include <stdio.h>
  #include <unistd.h>
  #include <fcntl.h>
  #include <errno.h>
  #include <linux/input.h>

  int main(int argc, char** argv)
  {
      int fd, bytes;
      struct input_event data;

      const char *pDevice = "/dev/input/event2";

      // Open Keyboard
      fd = open(pDevice, O_RDONLY | O_NONBLOCK);
      if(fd == -1)
      {
          printf("ERROR Opening %s\n", pDevice);
          return -1;
      }

      while(1)
      {
          // Read Keyboard Data
          bytes = read(fd, &data, sizeof(data));
          if(bytes > 0)
          {
              printf("Keypress value=%x, type=%x, code=%x\n", data.value, data.type, data.code);
          }
          else
          {
              // Nothing read
              sleep(1);
          }
      }

      return 0;
   }

The curses library can be used for this purpose. Of course, select() and signal handlers can be used too to a certain extent.

If you are happy just catching Control-C, it's a done deal. If you really want non-blocking I/O but you don't want the curses library, another alternative is to move lock, stock, and barrel to the AT&T sfio library. It's nice library patterned on C stdio but more flexible, thread-safe, and performs better. (sfio stands for safe, fast I/O.)

There is no portable way to do this, but select() might be a good way. See http://c-faq.com/osdep/readavail.html for more possible solutions.

Here's a function to do this for you. You need termios.h which comes with POSIX systems.

#include <termios.h>
void stdin_set(int cmd)
{
    struct termios t;
    tcgetattr(1,&t);
    switch (cmd) {
    case 1:
            t.c_lflag &= ~ICANON;
            break;
    default:
            t.c_lflag |= ICANON;
            break;
    }
    tcsetattr(1,0,&t);
}

Breaking this down: tcgetattr gets the current terminal information and stores it in t. If cmd is 1, the local input flag in t is set to non-blocking input. Otherwise it is reset. Then tcsetattr changes standard input to t.

If you don't reset standard input at the end of your program you will have problems in your shell.

You can do that using select as follow:

  int nfds = 0;
  fd_set readfds;
  FD_ZERO(&readfds);
  FD_SET(0, &readfds); /* set the stdin in the set of file descriptors to be selected */
  while(1)
  {
     /* Do what you want */
     int count = select(nfds, &readfds, NULL, NULL, NULL);
     if (count > 0) {
      if (FD_ISSET(0, &readfds)) {
          /* If a character was pressed then we get it and exit */
          getchar();
          break;
      }
     }
  }

Not too much work :D

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top