
Zxaos
Estoy tratando de escribir un programa en C (en Linux) que se repite hasta que el usuario presiona una tecla, pero no debería requerir que se presione una tecla para continuar con cada ciclo.
¿Hay una manera simple de hacer esto? Me imagino que podría hacerlo con select()
pero eso parece mucho trabajo.
Alternativamente, ¿hay alguna manera de atrapar un control–C presione la tecla para hacer la limpieza antes de que el programa se cierre en lugar de no bloquear io?

Alnitak
Como ya se ha dicho, puede utilizar sigaction
para atrapar ctrl-c, o select
para atrapar cualquier entrada estándar.
Tenga en cuenta, sin embargo, que con el último método también necesita configurar el TTY para que esté en modo carácter a la vez en lugar de línea a la vez. Este último es el valor predeterminado: si escribe una línea de texto, no se envía a la entrada estándar del programa en ejecución hasta que presione Intro.
Necesitarías usar el tcsetattr()
función para desactivar el modo ICANON, y probablemente también desactivar ECHO. ¡De memoria, también debe volver a configurar el terminal en el modo ICANON cuando finalice el programa!
Solo para completar, aquí hay un código que acabo de eliminar (nota: ¡no hay verificación de errores!) Que configura un TTY de Unix y emula el DOS <conio.h>
funciones kbhit()
y getch()
:
#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) > 0;
}
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 */
}
Puede hacerlo usando select de la siguiente manera:
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;
}
}
}
No es mucho trabajo 😀
select()
es un poco demasiado bajo para mayor comodidad. Te sugiero que uses el ncurses
biblioteca para poner el terminal en modo cbreak y modo de retraso, luego llame getch()
que volverá ERR
si ningún personaje está listo:
WINDOW *w = initscr();
cbreak();
nodelay(w, TRUE);
En ese momento puedes llamar getch
sin bloqueo.

Justin B
¡Otra forma de obtener una entrada de teclado sin bloqueo es abrir el archivo del dispositivo y leerlo!
Debe conocer el archivo del dispositivo que está buscando, uno de /dev/input/event*. Puede ejecutar cat /proc/bus/input/devices para encontrar el dispositivo que desea.
Este código funciona para mí (ejecutar como administrador).
#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;
}

112
Aquí hay una función para hacer esto por usted. Necesitas termios.h
que viene con los sistemas POSIX.
#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);
}
Desglosando esto: tcgetattr
obtiene la información actual del terminal y la almacena en t
. Si cmd
es 1, el indicador de entrada local en t
está configurado como entrada sin bloqueo. De lo contrario, se reinicia. Entonces tcsetattr
cambia la entrada estándar a t
.
Si no restablece la entrada estándar al final de su programa, tendrá problemas en su shell.

mmx
En los sistemas UNIX, puede utilizar sigaction
llamada para registrar un manejador de señales para SIGINT
señal que representa la secuencia de teclas Control+C. El controlador de la señal puede establecer una bandera que se verificará en el bucle para que se rompa de manera adecuada.

Mateo Italia
probablemente quieras kbhit();
//Example will loop until a key is pressed
#include <conio.h>
#include <iostream>
using namespace std;
int main()
{
while(1)
{
if(kbhit())
{
break;
}
}
}
esto puede no funcionar en todos los entornos. Una forma portátil sería crear un hilo de monitoreo y establecer alguna marca en getch();
que tal abrir un hilo?
– Nathan B.
26 de mayo de 2020 a las 14:10
Recomiendo un subproceso separado que bloquea la entrada del teclado y pasa la entrada al otro subproceso principal cada vez que se presiona una tecla. Aquí hay un ejemplo que escribí en Python. Para C, asegúrese de usar colas de transferencia de mensajes seguras para subprocesos, o bloquee la adquisición de mutex, según sea necesario. La biblioteca de colas de Python ya es segura para subprocesos, pero tal seguridad para las colas no existe naturalmente en C ni en C++.
– Gabriel grapas
3 de febrero a las 21:28
Ver también: ¿Cómo evitar presionar Enter con getchar() para leer un solo carácter?
– Gabriel grapas
3 de febrero a las 22:54