Intervals on input until CTRL + D

Viewed 932

I have intervals written as <a;b> which I read with scanf("<%d;%d>", &a, &b);, but the problem is I need to use my function for many such intervals until I press CTRL + Z or CTRL + D on Unix.

So far I tried something like this, but it doesn't work.

int main( void ) {
int a=0, b=0;

printf("Intervals:\n");
while(scanf("<%d;%d>", &a, &b) == 2 && !feof(stdin)){

    printf("Distinct rectangular cuboids: %d\n", capacities(a, b));

}
return 0;
2 Answers

You aren't concise with your problem. This:

int main( void ) { 
    int a=0, b=0;

    printf("Intervaly: \n");
    while(scanf("<%d;%d>",&a,&b) == 2){ 
        printf("Ruznych kvadru: %d\n", capacity(a, b));
    }   
    return 0;  
}

does in fact what you want, either with a long line of <%d;%d> or line by line - both will work. Your problem, hidden in the comments, that an EOF does not terminate this while immediately - rather you need to CTRL-D twice to stop it. The reason for that is also in the comments.

Once you scanf the way you do, you are going to be left with a \n in the input stream (because you have to press return to send your input without terminating). There are two ways to deal with this - add it in the scanf, which is less flexible and has some problems, or clear it in the while:

while(scanf("<%d;%d>",&a,&b) == 2){ 
    printf("Ruznych kvadru: %d\n", capacity(a, b));
    getchar();
}   

You will find now you need to press CTRL-D once. If you tried input redirection from a file you would have seen this problem didn't exist either. Why does it happen? You had \n in the input stream. You press EOF, so scanf gets a newline and ignores it - waiting for new input. Only now a new EOF will kill the scanf.

I suggest searching this site for scanf questions to see why not to use it, and other nice alternatives.

Addendum

A more elegant solution given in the comments and pointed out to me by @BluePixy and pointed out to me by @DavidBowling is to use for your scanf:

scanf("<%d;%d>%*c", &a, &b);

This will naturally gobble up the extra characters, including the new space, without the need for an extra getchar. This I think is the most elegant solution to this scanf usage.

Even more

Following the comments by OP and again by David (thanks again!) in order to account for 0 or more spaces, you just need a space directive in the formatting:

scanf("< %d ; %d >%*c", &a, &b);

Now spaces may (or may not) be added around each integer.

As for the loop condition, while( scanf(" <%d;%d>", &a, &b)) == 2 ) should be enough, but you should probably also keep track of the number for error diagnosis:

int nread=2;
while( (nread=scanf(" <%d;%d>", &a, &b)) == 2){ //...

The Ctrl-Z part is more complicated. Ctrl-Z causes canonically set terminal drivers to generate the SIGTSTP signal.

The default disposition of this signal is to put the process to sleep, which means you can't simply use ISO C's signal function to register a signal handler, because these signal handlers may inadvertently restore the original signal disposition if more than one instance of the signal is received, which would put your process to a halt.

You need to reach into POSIX and use sigaction.

The sigaction handler can then set a volatile flag, which will be a signal to terminate the loop. Since the reception of the signal while inside scanf will cause scanf to fail with EINTR, robust error handling for this is a little convoluted:

#include <stdio.h>
#include <signal.h>
#include <errno.h>

static int volatile ctrl_z;
void SignalHandler( int Signum )
{
    (void)Signum;
    ctrl_z=1;
}

int main( void ) {
    // ctrl + z
    sigaction(SIGTSTP, &(struct sigaction){ .sa_handler=SignalHandler }, 0);

    int a=0, b=0, nread=2;

    /*...*/
    while( (nread=scanf(" <%d;%d>", &a, &b)) == 2){

       /*...*/

        if(ctrl_z)
            return 0;
        errno=0; /*so we know if scanf failed because of the signal*/
    }
    if ( (!ferror(stdin) && feof(stdin)) ||
           (ferror(stdin) && errno==EINTR && ctrl_z) )
        return 0;
    return 1;
}
Related