20

There seem to be a LOT of ways you can get user input in C.

What is the easiest way that requires little code?

Basically I need to display this:

Enter a file name: apple.text

Basically I need to ask the user for a file name. So I need something that just gets that one word that the user will be inputting.

Mat
  • 202,337
  • 40
  • 393
  • 406
antonpug
  • 13,724
  • 28
  • 88
  • 129
  • Possible duplicate http://stackoverflow.com/questions/314401/how-to-read-a-line-from-the-console-in-c – Ray Toal Oct 20 '11 at 06:11

6 Answers6

23

The simplest "correct" way is probably this one, taken from Bjarne Stroustrup's paper Learning Standard C++ As A New Language.

(Note: I changed Bjarne's code to check for isspace() instead of just end of line. Also, due to @matejkramny's comment, to use while(1) instead of while(true)...and so long as we're being heretical enough to edit Stroustrup's code, I've subbed in C89 comments instead of C++ style too. :-P)

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

void quit() /* write error message and quit */
{
    fprintf(stderr, "memory exhausted\n");
    exit(1);
}

int main()
{
    int max = 20;
    char* name = (char*) malloc(max); /* allocate buffer */
    if (name == 0) quit();

    printf("Enter a file name: ");

    while (1) { /* skip leading whitespace */
        int c = getchar();
        if (c == EOF) break; /* end of file */
        if (!isspace(c)) {
             ungetc(c, stdin);
             break;
        }
    }

    int i = 0;
    while (1) {
        int c = getchar();
        if (isspace(c) || c == EOF) { /* at end, add terminating zero */
            name[i] = 0;
            break;
        }
        name[i] = c;
        if (i == max - 1) { /* buffer full */
            max += max;
            name = (char*) realloc(name, max); /* get a new and larger buffer */
            if (name == 0) quit();
        }
        i++;
    }

    printf("The filename is %s\n", name);
    free(filename); /* release memory */
    return 0;
}

That covers:

  • skipping whitespace until you reach character input
  • expanding the string buffer dynamically to fit arbitrary size strings
  • handling conditions of when memory can't be allocated

Are there simpler but broken solutions, which might even run a bit faster? Absolutely!!

If you use scanf into a buffer with no limit on the read size, then your input exceeds the size of the buffer, it will create a security hole and/or crash.

Limiting the size of the reading to, say, only 100 unique characters of a filename might seem better than crashing. But it can be worse; for instance if the user meant (...)/dir/foo/bar.txt but you end up misinterpreting their input and overwriting a file called bar.t which perhaps they cared about.

It's best to get into good habits early in dealing with these issues. My opinion is that if your requirements justify something close-to-the-metal and "C-like", it's well worth it to consider the jump to C++. It was designed to manage precisely these concerns--with techniques that are robust and extensible, yet still perform well.

Anny G
  • 159
  • 2
  • 2
  • 11
  • Even if your class wants you to scanf and move on, I'd suggest putting notes anywhere you knowingly do something broken/expedient. So next to your read operations just stick in a comment like `/* REVIEW: input correctness, see http://stackoverflow.com/questions/7831755/what-is-the-simplest-way-of-getting-user-input-in-c/ */` It shows you do your research, keeps the link for later reading. Plus anyone debugging a codebase who is getting a mystery crash can have their attention drawn--seeing not only the warning but a pointer to a discussion of the correct solution. – HostileFork says dont trust SE Oct 20 '11 at 07:03
  • 2
    Btw the question asks for C not C++ – Matej Sep 23 '14 at 21:32
  • 1
    @matejkramny In the paper I cite and quote, Bjarne Stroustrup contrasts the "C way" to the "C++ way". This is his "C way" code that I am presenting. It is not C++ code. Point of the paper is that this "correct" C code can be expressed in C++ more saliently and with no loss of efficiency; which may surprise die-hard C programmers. I assure you I can read the question; I just thought one of the best answers I'd read to how to write the "correct" C version of what C++ does for you by default came from a paper written that was a work of C++ evangelism. You should read the paper, though it's old. – HostileFork says dont trust SE Sep 23 '14 at 22:38
  • C++ has boolean defined, which don't exist (by default) in C. Thats why I thought this is C++ code, for a C question – Matej Sep 23 '14 at 22:44
  • I'd also like to know why there are two while loops, it seems you only need one? (pardon my ignorance) – Matej Sep 23 '14 at 22:45
  • @matejkramny I could mention that C99 has stdbool.h...except for the fact that I don't know what you are talking about as there is no `bool` in this answer. The first loop skips leading whitespace. Please read [Learning Standard C++ as a New Language](http://www.stroustrup.com/new_learning.pdf) if you'd like this code explained, as it covers this example in C and C++ and others. – HostileFork says dont trust SE Sep 24 '14 at 04:12
  • There is `while(true)` which my compiler whined about. I understand the stuff now though, thanks :) – Matej Sep 24 '14 at 08:49
  • @matejkramny Ah, missed that--thanks for pointing it out! I guess Bjarne's compiler didn't complain. Modified to use `while(1)` instead. – HostileFork says dont trust SE Sep 24 '14 at 10:22
  • Also, i feel that `malloc` and `realloc` are not allocating enough memory. It should be `malloc(max * sizeof(char))` no? Additionally, C doesn't care that `malloc` returns `void*` so the implicit conversion is not required. – Matej Sep 24 '14 at 11:08
  • @matejkramny [sizeof(char) is always 1](http://stackoverflow.com/a/2215454/211160). As for the `(char*)` cast not being necessary, it doesn't *hurt* to have it...and if someone is in the habit of writing both C and C++ code then putting in such casts makes it easier if one often switches between languages, or is writing code in the (extremely large) subset of C that can also be compiled correctly with C++. – HostileFork says dont trust SE Sep 24 '14 at 11:14
  • There needs to be a curly brace on this line. I can't edit it in since it's just one character: if (isspace(c) || c == EOF) /* at end, add terminating zero */ – DaleSwanson Sep 05 '15 at 21:42
  • `EOF` can be caused by error. POSIX specifies following errors: `EAGAIN`, `EBADF`, `EINTR`, `EIO`, `EOVERFLOW`, `ENOMEM` and `ENXIO`. The `ferror()` or `feof()` functions must be used to distinguish between an error condition and an end-of-file condition. – patryk.beza Oct 01 '15 at 09:11
7

scanf seems to work in a non-hostile environment. In other words, you're making a simple utility program for yourself.

BUFSIZ usually far exceeds the size limits of UNIX pathnames.

#include <stdio.h>

int main(void) {
  char str[BUFSIZ];                                                                                                                                                                             

  printf("Enter a file name: ");                                                                                                                                                                
  scanf("%s", str);                                                                                                                                                                             

  printf("You entered: %s\n", str);                                                                                                                                                             
}

If you need to accumulate data in a program that could be the target of buffer overrun, you might need a bit more.

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

char * prompt_and_read(const char * prompt) {
  char * response;
  char * bufsize;

  printf("%s", prompt);
  asprintf(&bufsize, "%%%us", BUFSIZ);

  if((response = malloc(BUFSIZ + 1)) == NULL) {
    fprintf(stderr,"out of memory\n");
    exit(1);
  }
  scanf(bufsize, response);
  free(bufsize);
  return response;
}

int main ( void ) {
  char * pathname;

  pathname = prompt_and_read("Enter a file name: ");
  printf("You entered: [%s]\n", pathname);
  free(pathname);

  return 0;
}
karrick
  • 382
  • 1
  • 4
3
#include <stdio.h>
int main(void)
{
    char file[100];
    printf("File Name?: \n");
    fgets(file, 100, stdin);
    printf("Your input: %s", file);
}
Tio Pepe
  • 3,071
  • 1
  • 17
  • 22
1
#include <iostream>
#include <string>

using namespace std;

int main()
{
    cout << "Enter a file name:";

    string filepath;
    cin >> filepath;
}
Ayjay
  • 3,413
  • 15
  • 20
1

you should write your own fgets() wrapper function that reads a line of input into a buffer of a specified size and that removes the newline char that fgets() also reads. you could also return a status of whether or not the whole line was able to fit into the buffer (ie. is the newline at the end of the buffer). you shouldn't ever really use scanf or gets unless you want overflows.

EDIT: thought i might provide some basic code:

typedef unsigned char BOOL;
#define TRUE   1
#define FALSE  0

/* wraps fgets, returns FALSE if the whole line couldn't fit into the buffer */
BOOL read_input(char *dest, size_t len)
{
   /* our temp buffer needs to hold the extra new line char (len + 1)
        * should also add error checking to malloc */
   BOOL bSuccess = TRUE;
   size_t total_len = len + 1;
   char *temp = (char*) malloc( total_len * sizeof(char) );

   fgets(temp, total_len, stdin);

   /* if the whole line wasn't read, we'll return FALSE but still copy temp into dest */
   if (temp[strlen(temp) - 1] != '\n')
      bSuccess = FALSE;
   else
      temp[strlen(temp) - 1] = '\0'; /* overwrite the '\n' if it does infact exist */

   strcpy(dest, temp);

   free(temp);
   return bSuccess;
}
AusCBloke
  • 18,014
  • 6
  • 40
  • 44
  • Well this is for a simple (well ok, not so simple) programming assignment. Just want to pass the class at this point. Not going to over complicate. Thanks though! – antonpug Oct 20 '11 at 06:34
  • The above can easily be done in under 10 lines, it really depends on how many times you'd need to repeat the functionality. Depending on the use of your input, that newline char can also sometimes cause problems. And it's also worth noting that if fgets is unable to fit the whole line into your buffer you might want to remove the extra input up until you encounter a newline (fgetc in a loop). – AusCBloke Oct 20 '11 at 06:40
  • `scanf` has character limits, such as `scanf("%99s", mySize100Buffer);`. But unlike `fgets` the bound is specified by a digit string, so capturing the BUFSIZE-1 relationship involves generating a custom format string. Somewhat impractical... – HostileFork says dont trust SE Oct 20 '11 at 07:00
  • EDIT: here's some code to show how simple you could have it (don't copy it so you don't get busted for plagiarism). – AusCBloke Oct 20 '11 at 07:01
0

Use this simple code

#include"stdio.h"
#include"stdlib.h"
main()
{
    int i=0,j=0;
    char c,buf[100];

start:
    printf("Enter the name:");
    while ( (c=getchar()) != '\n' )
    {
            buf[i++]=c;
            buf[i]='\0';
    }
    if ( buf[0] == '\0')
    {
    printf("invalid name\n");
    goto start;
    }
    else
    printf("Your name is valid\nName = %s\n",buf);
      }                                                                                                                             
Q_SaD
  • 355
  • 1
  • 11