I have an exercise where I am required to print a file slowly (1 second intervals) until the file ends, unless the user types a character.
So far, the program outputs the file in one second intervals which is great, but when I type a character, nothing happens. My guess is that I am using select wrong somehow.
This is the final program I ended up submitting.
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
FILE* infile;
char str[100];
fd_set readset;
struct timeval tv;
// open a file
if((infile = fopen("infile", "r")) == NULL)
{
(void)printf("Couldn't open the file\n");
exit(1);
}
// file was opened successfully
else
{
// while we are not at the end of a file
while(fgets(str, 100, infile) != NULL)
{
FD_ZERO(&readset);
FD_SET(fileno(stdin), &readset);
// set the time value to 1 second
tv.tv_sec = 1;
tv.tv_usec = 0;
select(fileno(infile)+1, &readset, NULL, NULL, &tv);
// the user typed a character so exit
if(FD_ISSET(fileno(stdin), &readset))
{
fclose(infile);
exit(0);
}
// the user didn't type a character so print the next line
else
{
fgets(str, 100, stdin);
puts(str);
}
}
// clean up
fclose(infile);
}
// report success
return 0;
}
Thanks for the help!
This is a working version, using tcgetattr/tcsetattr:
#include <stdio.h>
#include <stdlib.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <termios.h>
int main(void) {
FILE* infile;
char str[100];
fd_set readset;
struct timeval tv;
struct termios ttystate, ttysave;
// open a file
if((infile = fopen("infile", "r")) == NULL)
{
(void)printf("Couldn't open the file\n");
exit(1);
}
// file was opened successfully
//get the terminal state
tcgetattr(STDIN_FILENO, &ttystate);
ttysave = ttystate;
//turn off canonical mode and echo
ttystate.c_lflag &= ~(ICANON | ECHO);
//minimum of number input read.
ttystate.c_cc[VMIN] = 1;
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
// while we are not at the end of a file
while(fgets (str, 100, infile))
{
// set the time value to 1 second
tv.tv_sec = 1;
tv.tv_usec = 0;
FD_ZERO(&readset);
FD_SET(fileno(stdin), &readset);
select(fileno(stdin)+1, &readset, NULL, NULL, &tv);
// the user typed a character so exit
if(FD_ISSET(fileno(stdin), &readset))
{
fgetc (stdin); // discard character
break;
}
// the user didn't type a character so print the next line
else
{
puts(str);
// not needed: sleep(1);
}
}
// clean up
fclose(infile);
ttystate.c_lflag |= ICANON | ECHO;
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &ttysave);
// report success
return 0;
}
The sleep(1);
isn't needed anymore.
The terminal is buffering lines. It doesn't send text to the program until you press the Enter key. There might be a way to disable terminal line buffering, but I imagine it is beyond the scope of your assignment.
It stops when you press Enter. However, it doesn't quit immediately. That's something you'll want to fix. Get rid of that sleep(1)
.
Now your program spams text! You gave select
a timeout of one second, didn't you?
// set the time value to 1 second
tv.tv_sec = 1;
tv.tv_usec = 0;
The reason the timeout doesn't stick is because select
is modifying the timeout value. From the man page:
On Linux, select() modifies timeout to reflect the amount of time not slept; most other implementations do not do this. (POSIX.1-2001 permits either behavior.) This causes problems both when Linux code which reads timeout is ported to other operating systems, and when code is ported to Linux that reuses a struct timeval for multiple select()s in a loop without reinitializing it. Consider timeout to be undefined after select() returns.
You will need to initialize the timeval
before every call to select, not just once at the beginning of the program.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With