cbuffernewlineflush

C Program Pauses After Reading First Input


I'm working on a C program intended to remove blank lines between text from a .txt file. The program asks the user for the names of the source and destination files. However, I'm encountering an issue where the program seems to pause or hang after receiving the name of the source file. It only continues on to asking forr the destination file name AFTER the user hits enter a second time. I've tried using scanf and fgets, and also attempted to flush the stdin buffer, but the problem persists. I'm using the exact code for grabbing file names as my last 3-4 labs so I am clueless on what is wrong. The program ends when it gets to the first fopen. The if statment at this point runs and is True.. so the source file name is somehow being misinterpreted.

Here's the code:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int main() {
    FILE *source, *destination;
    char source_file_name[256], destination_file_name[] = "output.txt", buffer[1024];

    // Prompts user for file name to search, reads the name into our buffer, and stores the file name. Will also clear the buffer.
    printf("Enter the source file name: "); 
    fgets(buffer, sizeof(buffer), stdin);
    sscanf(buffer, "%s", source_file_name);

    // Prompts user for file name to use for the copy.
    printf("\nWhat would you like to name the duplicate file? (default name: output.txt): ");
    fgets(buffer, sizeof(buffer), stdin);
    if(strlen(buffer) > 1) { // If a name is entered, it is used. If not, the default is used.
        sscanf(buffer, "%s", destination_file_name);
    }
    source = fopen(source_file_name, "r");
    if(!source) {
        perror("The source file was not found.");
        return 1;
    }

    destination = fopen(destination_file_name, "w");
    if(!destination) 
    {
        perror("Could not create the destination file.");
        fclose(source); // Close the source file before exiting.
        return 1;
    }

    while (fgets(buffer, sizeof(buffer), source)) {
        char *p = buffer;
        int blank_space = 1;
        while(*p != '\0') {
            if (!isspace((unsigned char) *p)) {
                blank_space = 0;
                break;
            }
            p++;
        }
        if(!blank_space) {
            fputs(buffer, destination);
        }
    }

    fclose(source);
    fclose(destination);

    return 0;   
}

Observed Behavior:

After entering the source file name and pressing enter, the program doesn't immediately prompt for the destination file name. It seems to wait until I press enter again.

Attempted Fixes:

OS: Windows 11

IDE: VS Code


Solution

  • The only issue I see with your code is the buffer overflow that @JonathanLeffler mentioned earlier.

    If any line is longer than 1023 bytes then you may be deleting spaces in the middle of a very long line. You should document this or even better generate an error if that assumption is invalidated (you can use the return value from all() to figure out if you got a long string; worst case you can't print the line till you see the last character of an arbitrary long line).

    To improve readability I introduced prompt_and_open(). This also reduces the scope of the file name to this function, and the bounce via buffer to remove the trailing newline. It's also better UX to check if the source is available before prompting for the destination file name. Readability was also the reason why I introduced the all() function. Magic values are replaced by symbolic constants and it also addresses the potential buffer overflow.

    Resource cleanup could be identical in the normal and error case but I prefer code duplication to carry a return value throughout main() (i.e. goto err is better than rv = 1; goto out;).

    #define _POSIX_C_SOURCE 200809L
    #include <ctype.h>
    #include <stdio.h>
    #include <string.h>
    
    #define BUFFER_LEN 1024
    #define DESTINATION "output.txt"
    #define DESTINATION_PROMPT "What would you like to name the duplicate file?"
    #define NAME_LEN 256
    #define SOURCE_PROMPT "Enter the source file name"
    
    const char *all(const char *s, int (*f)(int)) {
        for(; *s; s++)
            if(!f(*s))
                return NULL;
        return s;
    }
    
    FILE *prompt_and_open(const char *prompt, const char *fallback, char *mode) {
        printf("%s", prompt);
        if(fallback)
            printf(" (default %s)", fallback);
        printf(": ");
        char name[NAME_LEN];
        fgets(name, NAME_LEN, stdin);
        name[strcspn(name, "\n")] = '\0';
        if(fallback && !*name)
            *stpncpy(name, fallback, NAME_LEN-1) = '\0';
        return fopen(name, mode);
    }
    
    int main(void) {
        FILE *source = NULL;
        FILE *destination = NULL; // simplifies error handling
    
        source = prompt_and_open(SOURCE_PROMPT, NULL, "r");
        if(!source) {
            perror("The source file was not found.");
            goto err;
        }
        destination = prompt_and_open(DESTINATION_PROMPT, DESTINATION, "w");
        if(!destination) {
            perror("Could not create the destination file.");
            goto err;
        }
        char buffer[BUFFER_LEN];
        while(fgets(buffer, sizeof buffer, source))
            if(!all(buffer, isspace))
                fputs(buffer, destination);
        fclose(source);
        fclose(destination);
        return 0;
    err:
        if(source) fclose(source);
        if(destination) fclose(destination);
        return 1;
    }