Thread: Problem writing to and reading from external text file

  1. #1
    Registered User
    Join Date
    Mar 2019
    Posts
    11

    Question Problem writing to and reading from external text file

    Hi,

    I am trying to develop a small programme that writes a three field "record" to an external text file and then reads that same data back from that external file.

    So far, the programme does write the "records" to the external file in a one-field-per-line format whereas I would prefer to have all three fields of a "record" on one line in the external file. The contents of the external file are shown below.

    Fred Zodiac
    12345
    3.8
    Barney Tempest
    23456
    3.5
    Wilma Shore
    34567
    3.7

    This clearly shows that I am having a problem understanding how to write data to an external file in the required three-fields-per-line format, in the function saveStudentInformation().

    The programme also does find a target "record" in the external file but displays it on-screen in the one-field-per-line format. Again this shows that I am having trouble understanding how to get printf() in function showStudentInformation() to display the data in the required three-fields-per-line format.

    The code is shown below.
    Code:
    /* Create a program that uses a menu with options to enter student information*
     * (name, ID, GPA), print student information, or quit the program. Use data      *
     * files and FILE pointers to store and print information entered.                         */
    
    #include <stdio.h>
    #include <stdlib.h>  // For fgets()
    #include <string.h>  // For strcpy()
    #include <ctype.h>   // For toupper()
    
    
    static void showMenu();
    static void enterStudentInformation(char [], char [], char []);
    static void saveStudentInformation(char [], char [], char []);
    static void findStudentInformation(char [], char [], char []);
    static void showStudentInformation(char [], char [], char []);
    
    
    int main()
    {
       char studentName[20] = "";
       char id[20] = "";
       char gpa[20] = "";
       char response;
       char newline;
    
       showMenu();
       (void) scanf("%c%c", &response, &newline);  // second %c swallows \n
       response = toupper(response);
    
       while (response != 'Q')
       {
          switch (response)
          {
             case 'A': enterStudentInformation(studentName, id, gpa);
                       saveStudentInformation(studentName, id, gpa);
                       break;
             case 'B': findStudentInformation(studentName, id, gpa);
                       showStudentInformation(studentName, id, gpa);
                       break;
          }
    
          showMenu();
          (void) scanf("%c%c", &response, &newline);
          response = toupper(response);
       }
       return 0;
    } // end main()
    
    
    void showMenu()
    {
       printf("\tMenu\n");
       printf("\nA. Enter student information. <A/a>: ");
       printf("\nB. Show student information. <B/b>: ");
       printf("\nC. Quit the program. <Q/q>: ");
    }
    
    
    void enterStudentInformation(char aStudentName[], char anID[], char aGPA[])
    {
       int stringSize = 19;
    
       printf("\nEnter student name (firstname lastname): ");
       (void) fgets(aStudentName, stringSize, stdin);
       printf("\nEnter student ID: ");
       (void) fgets(anID, stringSize, stdin);
       printf("\nEnter student gpa: ");
       (void) fgets(aGPA, stringSize, stdin);
    }
    
    
    void saveStudentInformation(char aStudentName[], char anID[], char aGPA[])
    {
       FILE * ptrWriteRecord;
    
       ptrWriteRecord = fopen("studentInformation.dat", "a");
       if (ptrWriteRecord != NULL)
       {
          fprintf(ptrWriteRecord, "%s%s%s", aStudentName, anID, aGPA);
          (void) fclose(ptrWriteRecord);
       }
       else
       {
          printf("\nFile studentInformation.dat cannot be opened.\n");
       }
    }
    
    
    void findStudentInformation(char aStudentName[], char anID[], char aGPA[])
    {
       FILE * ptrReadRecord;
       char temp[20] = "";
       int stringSize = 20;
    
       ptrReadRecord = fopen("studentInformation.dat", "r");
       if (ptrReadRecord != NULL)
       {
          printf("\nEnter student name (firstname lastname): ");
          (void) fgets(temp, stringSize, stdin);
    
          while (feof(ptrReadRecord) != EOF)
          {
             (void) fgets(aStudentName, stringSize, ptrReadRecord);
             if (strcmp(aStudentName, temp)== 0)
             {
                (void) fgets(anID, stringSize, ptrReadRecord);
                (void) fgets(aGPA, stringSize, ptrReadRecord);
                fclose(ptrReadRecord);
                return;  // student information has been found
             }
          }  // end while()
       }
       else
       {
          printf("\nFile studentInformation.dat cannot be opened.\n");
          fclose(ptrReadRecord);
       }
    }  // end findStudentInformation()
    
    
    void showStudentInformation(char aStudentName[], char anID[], char aGPA[])
    {
       printf("\nName\t\tID\t\tGPA\n");
       printf("%s%s%s\n", aStudentName, anID, aGPA);
    }

    How can I write a "record" to the external file in a three-fields-per-line format and then display the data on-screen in that same format. For reading the external file in function findStudentInformation() I have tried using both fgets() and fscanf(), to no avail.

    Stuart

  2. #2
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    The fgets() function reads a line including the new-line character. If you read from stdin, hitting the Enter key will create a new line in this case. Determine the length of the string, and overwrite the last character with '\0'.

  3. #3
    Registered User rstanley's Avatar
    Join Date
    Jun 2014
    Location
    New York, NY
    Posts
    1,110
    Quote Originally Posted by stuarte83 View Post
    Hi,

    I am trying to develop a small programme that writes a three field "record" to an external text file and then reads that same data back from that external file.

    So far, the programme does write the "records" to the external file in a one-field-per-line format whereas I would prefer to have all three fields of a "record" on one line in the external file. The contents of the external file are shown below.

    Fred Zodiac
    12345
    3.8
    Barney Tempest
    23456
    3.5
    Wilma Shore
    34567
    3.7

    This clearly shows that I am having a problem understanding how to write data to an external file in the required three-fields-per-line format, in the function saveStudentInformation().

    The programme also does find a target "record" in the external file but displays it on-screen in the one-field-per-line format. Again this shows that I am having trouble understanding how to get printf() in function showStudentInformation() to display the data in the required three-fields-per-line format.



    How can I write a "record" to the external file in a three-fields-per-line format and then display the data on-screen in that same format. For reading the external file in function findStudentInformation() I have tried using both fgets() and fscanf(), to no avail.

    Stuart
    A few things:

    Use the following code to remove the newline from the end of the input line.
    Code:
    #include <stdio.h>
    #include <string.h>
    
    void delnl(char *str)
    {
      int len = strlen(str);
    
      if(str[len - 1] == '\n')
      {
        str[len - 1] = '\0';
      }
    }
    If the entire program is in one .c file, no need to mark all the other functions as static. Not wrong, but not needed.
    Instead of casting all the fgets() and scanf() function calls as (void), you should receive the number of chars entered. What if the input line is only a newline?
    Once the newline at the end of the input string has been removed, if the format string fr printf() and fprintf() are the same, then the screen output, and file output should be the same.

    Start with these recommendations.
    Last edited by rstanley; 11-11-2022 at 11:15 AM. Reason: Inserted wrong code!

  4. #4
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    You may want to handle NULL and zero-length strings.
    Code:
    void delnl(char *const str){
      const size_t len = str ? strlen(str) : 0;
    
    
      if (len && str[len - 1] == '\n')
      {
        str[len - 1] = '\0';
      }
    }

  5. #5
    Registered User rstanley's Avatar
    Join Date
    Jun 2014
    Location
    New York, NY
    Posts
    1,110
    Quote Originally Posted by aGerman View Post
    You may want to handle NULL and zero-length strings.
    Code:
    void delnl(char *const str){
      const size_t len = str ? strlen(str) : 0;
    
    
      if (len && str[len - 1] == '\n')
      {
        str[len - 1] = '\0';
      }
    }
    I would handle both cases in the calling function when I capture the length of the string input, If NULL or only newline, no need to call delnl().

  6. #6
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    Quote Originally Posted by rstanley View Post
    I would handle both cases in the calling function when I capture the length of the string input, If NULL or only newline, no need to call delnl().
    Plus there's a simpler way to set the trailing newline to a null terminator than how delnl does it: str[strcspn(str, "\n")] = '\0';

  7. #7
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    strcspn - cppreference.com
    Bear in mind that because the second parameter is also a string, strcspn has a nested loop.
    Sure, it runs only once, but it's a loop nonetheless.

    Though you'd need to be processing GB of strings to begin to notice a difference, even on my ageing i7.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  8. #8
    Registered User
    Join Date
    Feb 2019
    Posts
    1,078
    Not standard, but if the '\n' appears only at the end of the string:
    Code:
    char *p = strrchr( str, '\n' );
    if ( p ) *p = '\0';

  9. #9
    Registered User
    Join Date
    Sep 2022
    Posts
    55
    Before rstanlay updated his comment I've briefly seen hin posting a piece of code that ignores characters that have not been read. This is actually also required for fgets if the input is longer than buffer size - 2. User input can be complicated
    Code:
    #include <stdbool.h>#include <stddef.h>
    #include <stdio.h>
    #include <string.h>
    
    
    /// @brief Remove a trailing new line character from a null-terminated string.
    ///
    /// @param str       Pointer to the first character of the string. (NULL does
    ///                  not crash)
    /// @param pNlFound  Pointer to a variable retrieving the information whether
    ///                  `str` had an appended new line character. (can be NULL)
    ///
    /// @return The new length of `str` is returned.
    static size_t delnl(char *const str, bool *const pNlFound)
    {
      if (pNlFound)
        *pNlFound = false;
    
    
      const size_t len = str ? strlen(str) : 0;
      if (!len)
        return len;
    
    
      char *const pLast = str + len - 1;
      if (*pLast == '\n')
      {
        if (pNlFound)
          *pNlFound = true;
    
    
        *pLast = '\0';
        return len - 1;
      }
    
    
      return len;
    }
    
    
    #define STR_SIZ 20 // size for a string of 19 characters + terminating null
    
    
    int main(void)
    {
      char myBuf[STR_SIZ] = {0};
      size_t len = 0;
      bool nlFound = false;
    
    
      // Iterate as long as Enter has been pressed without any other input.
      do
      {
        fputs("Enter something: ", stdout);
      } while (!(len = delnl(fgets(myBuf, STR_SIZ, stdin), &nlFound)));
    
    
      printf(
        "\n"
        "String content: '%s'\n"
        "%zu character(s) read.\n",
        myBuf, len
      );
    
    
      // Not finding the new line char at the end of the string is a strong
      // indicator that the input has been larger than the buffer.
      len = 0;
      if (!nlFound)
        while (getchar() != '\n') // Ignore the remaining characters in stdin.
          ++len; // We count them only for testing purposes.
    
    
      printf(
        "%zu character(s) ignored.\n",
        len
      );
    }

  10. #10
    Registered User
    Join Date
    May 2012
    Location
    Arizona, USA
    Posts
    948
    Quote Originally Posted by Salem View Post
    strcspn - cppreference.com
    Bear in mind that because the second parameter is also a string, strcspn has a nested loop.
    Sure, it runs only once, but it's a loop nonetheless.
    Yep, though because n is a fixed value (2 if you count the null terminator), it's really just O(1) rather than O(n) in time complexity.

    Here's my quick-and-dirty (and untested) version of strcspn() that takes a single character for the second parameter:

    Code:
    size_t str_length_or_match(const char *str, int c)
    {
        const char *s = str;
        while (*s != '\0' && *s != c) {
            ++s;
        }
        return s - str;
    }
    
    // use thusly:
    // str[str_length_or_match(str, '\n')] = '\0';
    Then again, it's possible that the system's implementation of strcspn() is still faster than this, even with the loop on the second parameter.

    Quote Originally Posted by flp1969 View Post
    Not standard, but if the '\n' appears only at the end of the string:
    Code:
    char *p = strrchr( str, '\n' );
    if ( p ) *p = '\0';
    As far as I know, strrchr() is standard so that's a portable way to do it.

  11. #11
    Registered User
    Join Date
    Feb 2019
    Posts
    1,078
    Sorry... my mistake strrchr() is, in fact, listed on ISO 9899.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Replies: 8
    Last Post: 05-05-2010, 02:43 PM
  2. Problem writing to external file
    By kristentx in forum C++ Programming
    Replies: 2
    Last Post: 09-06-2007, 01:48 PM
  3. Problem Writing to Text File
    By slowcoder in forum C Programming
    Replies: 2
    Last Post: 08-22-2007, 12:19 PM
  4. trouble with text file reading/writing
    By sbeehre in forum C Programming
    Replies: 25
    Last Post: 08-02-2006, 09:48 PM
  5. Problem reading from external file
    By djayz in forum C Programming
    Replies: 13
    Last Post: 03-24-2005, 01:15 PM

Tags for this Thread