4

How to accept set of strings as input in C and prompt the user again to re-enter the string if it exceeds certain length. I tried as below

#include<stdio.h>
int main()
{
    char arr[10][25]; //maximum 10 strings can be taken as input of max length 25
    for(int i=0;i<10;i=i+1)
    {
        printf("Enter string %d:",i+1);
        fgets(arr[i],25,stdin);
    }
}

But here fgets accepts the strings greater than that length too. If the user hits return, the second string must be taken as input. I'm new to C

Vamsi Simhadri
  • 318
  • 2
  • 8
  • 4
    Input to a separate, larger buffer. If the input contains no newline, it was truncated, so you should keep reading until it does, and discard it, as with whole lines that are too long. When you get an acceptable string, copy it to the array (probably [minus the newline](https://stackoverflow.com/questions/2693776/removing-trailing-newline-character-from-fgets-input/28462221#28462221)). – Weather Vane Sep 24 '22 at 13:56
  • 4
    ...remember that if `fgets()` truncates a line due to its length, the rest of the line is not discarded but remains in the input. `fgets` retains the newline, so if it isn't there (and the buffer is full) the line was too long. – Weather Vane Sep 24 '22 at 14:27
  • 1
    @WeatherVane I often approached this problem with a slightly larger buffer, yet that large buffer adds memory management and end-of-file issues. So I tried below: a solution that can use the original destination buffer. – chux - Reinstate Monica Sep 24 '22 at 16:45
  • @chux-ReinstateMonica I posted a solution as described in earlier comments. – Weather Vane Sep 24 '22 at 18:18

3 Answers3

1

How to accept string input only if it of certain length

Form a helper function to handle the various edge cases. Use fgets(), then drop the potential '\n' (which fgets() retains) and detect long inputs.

Some untested code to give OP an idea:

#include <assert.h>
#include <stdio.h>

// Pass in the max string _size_.
// Return NULL on end-of-file without input.
// Return NULL on input error.
// Otherwise return the buffer pointer.
char* getsizedline(size_t sz, char *buf, const char *reprompt) {
  assert(sz > 0 && sz <= INT_MAX && buf != NULL); // #1
  while (fgets(buf, (int) sz, stdin)) {
    size_t len = strlen(buf);
    // Lop off potential \n
    if (len > 0 && buf[--len] == '\n') {   // #2
      buf[len] = '\0';
      return buf;
    }
    // OK if next ends the line
    int ch = fgetc(stdin);
    if (ch == '\n' || feof(stdin)) {       // #3 
      return buf;
    }

    // Consume rest of line;
    while (ch != '\n' && ch != EOF) {      // #4
      ch = fgetc(stdin);
    }
    if (ch == EOF) {                       // #5
      return NULL;
    }

    if (reprompt) {
      fputs(reprompt, stdout);
    }
  }
  return NULL;
}

Uncommon: reading null characters remains a TBD issue.

Details for OP who is a learner.

  1. Some tests for sane input parameters. A size of zero does not allow for any input saved as a null character terminated string. Buffers could be larger than INT_MAX, but fgets() cannot directly handle that. Code could be amended to handle 0 and huge buffers, yet leave that for another day.

  2. fgets() does not always read a '\n'. The buffer might get full first or the last line before end-of-file might lack a '\n'. Uncommonly a null character might be read - even the first character hence the len > 0 test, rendering strlen() insufficient to determine length of characters read. Code would need significant changes to accommodate determining the size if null character input needs detailed support.

  3. If the prior fgets() filled its buffer and the next read character attempt resulted in an end-of-file or '\n', this test is true and is OK, so return success.

  4. If the prior fgetc() resulted in an input error, this loops exits immediately. Otherwise, we need to consume the rest of the line looking for a '\n' or EOF (which might be due to an end-of-file or input error.)

  5. If EOF returned (due to an end-of-file or input error), no reason to continue. Return NULL.

Usage

// fgets(arr[i],25,stdin);
if (getsizedline(arr[i], sizeof(arr[i]), "Too long, try again.\n") == NULL) {
  break;
}
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
  • I think this might be overboard trying to kluge a function to do stuff it wasn’t meant to. `fgets` is for textual input. If you are working in a secure environment where bad input (nulls in the input stream, for example) is an issue, then don’t use `fgets`. Otherwise you can safely accept that some input may be lost to your program, up to and including making your program fail because you hit EOF or simply ran out of need to read input. – Dúthomhas Sep 24 '22 at 17:58
  • @Dúthomhas Re: "this might be overboard": This answer specifically mentions it has unaddressed issues with proper handling of _null characters_. Function does not have overbroad kluge concerns. The presence of _null characters_ is not only a secure environment concern. _Null characters_ comes up in UTF16 text files. It easy for a user to errantly attempt to redirect input from those. This function does not function well with `'\0'` other than it does not exhibit UB which can happen without the `len > 0`. IOWs `len > 0` is the only part of this code that relates to reading null characters. – chux - Reinstate Monica Sep 24 '22 at 19:33
  • You’re... trying to read UTF-16 data with `fgets()`? – Dúthomhas Sep 24 '22 at 20:48
  • @Dúthomhas No, code is not designed for reading UT16. It is just that some text files are UTF16 and reading such files should not incur UB, just simply not work right. – chux - Reinstate Monica Sep 24 '22 at 21:15
  • Agreed. I just think that that condition ought to be handled _before_ we get to trying to read the file with functions like this. – Dúthomhas Sep 24 '22 at 21:19
0

This code uses a buffer slightly larger than the required max length. If a text line and the newline can't be read into the buffer, it reads the rest of the line and discards it. If it can, it again discards if too long (or too short).

#include <stdio.h>
#include <string.h>
#include <stdbool.h>

#define INPUTS  10
#define STRMAX  25

int main(void) {
    char arr[INPUTS][STRMAX+1];
    char buf[STRMAX+4];
    for(int i = 0; i < INPUTS; i++) {
        bool success = false;
        while(!success) {
            printf("Enter string %d: ", i + 1);
            if(fgets(buf, sizeof buf, stdin) == NULL) {
                exit(1);                    // or sth better
            }

            size_t index = strcspn(buf, "\n");
            if(buf[index] == '\0') {        // no newline found
                // keep reading until end of line
                while(fgets(buf, sizeof buf, stdin) != NULL) {
                    if(strchr(buf, '\n') != NULL) {
                        break;
                    }
                }
                if(feof(stdin)) {
                    exit(1);                // or sth better
                }
                continue;
            }

            if(index < 1 || index > STRMAX) {
                continue;                   // string is empty or too long
            }

            buf[index] = '\0';              // truncate newline
            strcpy(arr[i], buf);            // keep this OK string
            success = true;
        }
    }
    
    printf("Results:\n");
    for(int i = 0; i < INPUTS; i++) {
        printf("%s\n", arr[i]);
    }
    return 0;
}
Weather Vane
  • 33,872
  • 7
  • 36
  • 56
  • If the last _line_ of input is short enough yet is ended with an end-of-line condition and not a `'\n'`, this answer does not treat that line as success. Of course the last with without a `'\n'` is an implementation defined concern. – chux - Reinstate Monica Sep 24 '22 at 19:39
  • @chux-ReinstateMonica that could be the case with input redirected from file, but otherwise the input is unlikely to terminate without a newline. – Weather Vane Sep 24 '22 at 19:42
  • Perhaps unlikely, yet not that difficult to detect and handle. – chux - Reinstate Monica Sep 24 '22 at 19:44
  • Corner case: `while(fgets(buf,...) { if(feof(stdin))` test disregards `fgets()` returning `NULL` due to input error. Of course input errors are even rarer the above concern. – chux - Reinstate Monica Sep 24 '22 at 19:45
  • Ideally you are right, and the string length should also be checked. As stated in original comment, if the buffer isn't full and lacks a newline, it's OK. – Weather Vane Sep 24 '22 at 19:46
0

The nice thing about fgets() is that it will place the line-terminating newline character ('\n') in the input buffer. All you have to do is look for it. If it is there, you got an entire line of input. If not, there is more to read.

The strategy then, is:

fgets( s, size_of_s, stdin );
char * p = strpbrk( s, "\r\n" );
if (p)
{
  // end of line was found.
  *p = '\0';
  return s; (the complete line of input)
}

If p is NULL, then there is more work to do. Since you wish to simply ignore lines that are too long, that is the same as throwing away input. Do so with a simple loop:

int c;
do c = getchar(); while ((c != EOF) && (c != '\n'));

Streams are typically buffered behind the scenes, either by the C Library or by the OS (or both), but even if they aren’t this is not that much of an overhead. (Use a profiler before playing “I’m an optimizing compiler”. Don’t assume bad things about the C Library.)

Once you have tossed everything you didn’t want (to EOL), make sure your input isn’t at EOF and loop to ask the user to try again.

Putting it all together

char * prompt( const char * message, char * s, size_t n )
{
  while (!feof( stdin ))
  {
    // Ask for input
    printf( "%s", message );
    fflush( stdout );  // This line _may_ be necessary.

    // Attempt to get an entire line of input
    if (!fgets( s, n, stdin )) break;
    char * p = strpbrk( s, "\r\n" );

    // Success: return that line (sans newline character(s)) to the user
    if (p)
    {
      *p = '\0';
      return s;
    }

    // Failure: discard the remainder of the line before trying again
    int c;
    do c = getchar(); while ((c != EOF) && (c != '\n'));
  }

  // If we get this far it is because we have 
  // reached EOF or some other input error occurred.
  return NULL;
}

Now you can use this utility function easily enough:

char user_name[20];  // artificially small

if (!prompt( "What is your name (maximum 19 characters)? ", user_name, sizeof(user_name) )) 
{
  complain_and_quit(); 
  // ...because input is dead in a way you likely cannot fix.
  // Feel free to check ferror(stdin) and feof(stdin) for more info.
}

This little prompt function is just an example of the kinds of helper utility functions you can write. You can do things like have an additional prompt for when the user does not obey you:

What is your name? John Jacob Jingleheimer Schmidt
Alas, I am limited to 19 characters. Please try again:
What is your name? John Schmidt
Hello John Schmidt.

Dúthomhas
  • 8,200
  • 2
  • 17
  • 39
  • Input that fits in `message[]` without a `'\n'`, yet end due to end-of-file are not treated as successful here. A reasonable concern with re-directed input. Rare: if `c = getchar();` returns `EOF` due to an input error, code errantly continues as input errors are not certainly sticky. – chux - Reinstate Monica Sep 24 '22 at 19:52
  • Wut? ① `message` is marked `const`. ② Text files are expected to end with newline, and always have been. ③ `fgets()` still functions properly when they don’t; consequently this function does as well. ④ No, `fgets()` and `getchar()` both work on byte input. Any error that magically resolves itself between the two invocations is a bigger problem than this code can be reasonably expected to handle. ⑤ Why U angry wig? – Dúthomhas Sep 24 '22 at 20:41
  • 1) Typo, should have been in `s[]` without a `'\n'`. 2) "Text files are expected to end with newline, and always have been." lacks support. C spec has "A text stream is an ordered sequence of characters composed into _lines_, each line consisting of zero or more characters plus a terminating new-line character. Whether the last line requires a terminating new-line character is implementation-defined." which clearly allows an implementation to decide. 3) "when they don’t;" is unclear – chux - Reinstate Monica Sep 24 '22 at 21:24
  • 4) As input errors are not "sticky" and its not hard to code that once `EOF` occurs, code does not attempt further reading, it is not that hard to handle. 5) No anger. The issue is about the code and answer, not the answerer. Best to keep it that way. – chux - Reinstate Monica Sep 24 '22 at 21:25
  • A limitation to this answer is that since the user does not want to save the `'\n'` in the final target buffer, to read `"John Schmidt"` into `char user_name[13]` requires a buffer of 14 to temporarily read and save a `'\n'`. – chux - Reinstate Monica Sep 24 '22 at 21:30