Как прочитать строчку из консоли на C?

108

Каков самый простой способ прочитать всю строку в программе консоли C Введенный текст может иметь переменную длину, и мы не можем делать никаких предположений относительно его содержания.

pbreault
источник
не могли бы вы уточнить, пожалуйста? как @Tim сказал ниже, это сбивает с толку то, о чем вы просите :)
Уоррен

Ответы:

81

Вам нужно динамическое управление памятью и используйте эту fgetsфункцию для чтения вашей строки. Однако, похоже, нет никакого способа увидеть, сколько символов он прочитал. Итак, вы используете fgetc:

char * getline(void) {
    char * line = malloc(100), * linep = line;
    size_t lenmax = 100, len = lenmax;
    int c;

    if(line == NULL)
        return NULL;

    for(;;) {
        c = fgetc(stdin);
        if(c == EOF)
            break;

        if(--len == 0) {
            len = lenmax;
            char * linen = realloc(linep, lenmax *= 2);

            if(linen == NULL) {
                free(linep);
                return NULL;
            }
            line = linen + (line - linep);
            linep = linen;
        }

        if((*line++ = c) == '\n')
            break;
    }
    *line = '\0';
    return linep;
}

Примечание : никогда не используйте получает! Он не выполняет проверку границ и может переполнить ваш буфер

Йоханнес Шауб - litb
источник
Предостережение - там нужно проверить результат перераспределения. Но если это не удается, то, скорее всего, есть более серьезные проблемы.
Тим
4
Вероятно, вы могли бы немного повысить эффективность, выполнив fgets с буфером и проверив, есть ли у вас символ новой строки в конце. Если вы этого не сделаете, перераспределите буфер накопления, скопируйте в него и снова fgets.
Пол Томблин,
3
Эта функция требует исправления: строка "len = lenmax;" после realloc должен либо предшествовать realloc, либо должен быть "len = lenmax >> 1;" - или какой-либо другой эквивалент, учитывающий тот факт, что половина длины уже используется.
Мэтт Галлахер,
1
@Johannes, отвечая на ваш вопрос, подход @ Paul МОЖЕТ быть быстрее в большинстве (т.е. реентерабельных) реализаций libc, поскольку ваш подход неявно блокирует stdin для каждого символа, тогда как его блокирует его один раз для каждого буфера. Вы можете использовать менее переносимый, fgetc_unlockedесли безопасность потоков не является проблемой, а производительность.
vladr
3
Обратите внимание, что это getline()отличается от стандартной getline()функции POSIX .
Джонатан Леффлер,
28

Если вы используете библиотеку GNU C или другую POSIX-совместимую библиотеку, вы можете использовать ее getline()и передать stdinей для потока файлов.

дмитюгов
источник
16

Очень простая, но небезопасная реализация для чтения строки для статического распределения:

char line[1024];

scanf("%[^\n]", line);

Более безопасная реализация без возможности переполнения буфера, но с возможностью не читать всю строку:

char line[1024];

scanf("%1023[^\n]", line);

Не «разница на единицу» между длиной, указанной при объявлении переменной, и длиной, указанной в строке формата. Это исторический артефакт.

Джонатан Леффлер
источник
14
Это совсем не безопасно. Он страдает от той же проблемы, почему getsбыл полностью удален из стандарта
Антти Хаапала
6
Примечание модератора: приведенный выше комментарий относится к предыдущей редакции ответа.
Роберт Харви
13

Итак, если вы искали аргументы команды, взгляните на ответ Тима. Если вы просто хотите прочитать строку с консоли:

#include <stdio.h>

int main()
{
  char string [256];
  printf ("Insert your full address: ");
  gets (string);
  printf ("Your address is: %s\n",string);
  return 0;
}

Да, это небезопасно, вы можете делать переполнение буфера, он не проверяет конец файла, он не поддерживает кодировки и многое другое. На самом деле я даже не подумал, сделал ли он что-нибудь из этого. Согласен, я напортачил :) Но ... когда я вижу вопрос типа «Как прочитать строку с консоли на C?», Я предполагаю, что человеку нужно что-то простое, например, gets (), а не 100 строк кода как указано выше. На самом деле, я думаю, что если вы попытаетесь написать эти 100 строк кода на самом деле, вы сделаете намного больше ошибок, чем если бы вы выбрали метод get;)

Павел Капустин
источник
1
Это не позволяет использовать длинные строки ... - что, я думаю, и составляет суть его вопроса.
Тим
2
-1, gets () использовать не следует, поскольку он не выполняет проверку границ.
расслабьтесь
7
С другой стороны, если вы пишете программу для себя и вам просто нужно прочитать ввод, это нормально. Степень защиты, которая требуется программе, является частью спецификации - вы не должны каждый раз ставить ее в приоритет.
Мартин Беккет,
4
@Tim - я хочу сохранить всю историю :)
Пол Капустин
4
Проголосовали против. getsбольше не существует, поэтому в C11 это не работает.
Антти Хаапала
11

Возможно, вам потребуется использовать посимвольный цикл (getc ()), чтобы убедиться, что у вас нет переполнения буфера и не усечение ввода.

Тим
источник
9

getline исполняемый пример

Упоминается в этом ответе, но вот пример.

Это POSIX 7 , он выделяет нам память и повторно использует выделенный буфер в цикле.

Pointer newbs, прочтите это: Почему первый аргумент getline является указателем на указатель «char **» вместо «char *»?

#define _XOPEN_SOURCE 700

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

int main(void) {
    char *line = NULL;
    size_t len = 0;
    ssize_t read = 0;
    while (read != -1) {
        puts("enter a line");
        read = getline(&line, &len, stdin);
        printf("line = %s", line);
        printf("line length = %zu\n", read);
        puts("");
    }
    free(line);
    return 0;
}

реализация glibc

Нет POSIX? Возможно, вы захотите взглянуть на реализацию glibc 2.23 .

Он разрешается в getdelim, который является простым расширением POSIX getlineс произвольным ограничителем строки.

Он удваивает выделенную память всякий раз, когда требуется ее увеличение, и выглядит поточно-ориентированным.

Это требует некоторого расширения макроса, но вряд ли вы добьетесь большего успеха.

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
Какова цель lenздесь, при чтении также предоставляется длина
Абдул
@ Абдул см man getline. len- это длина существующего буфера, 0является магической и сообщает ему о выделении. Чтение - это количество прочитанных символов. Размер буфера может быть больше, чем read.
Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
6

Многие люди, как и я, приходят к этому посту с заголовком, совпадающим с искомым, хотя в описании говорится о переменной длине. В большинстве случаев мы заранее знаем длину.

Если вы заранее знаете длину, попробуйте следующее:

char str1[1001] = { 0 };
fgets(str1, 1001, stdin); // 1000 chars may be read

источник: https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm

Манохар Редди Поредди
источник
5

Как было предложено, вы можете использовать getchar () для чтения с консоли до тех пор, пока не будет возвращен конец строки или EOF, создав свой собственный буфер. Динамическое увеличение буфера может происходить, если вы не можете установить разумный максимальный размер строки.

Вы также можете использовать fgets как безопасный способ получить строку как строку C с завершающим нулем:

#include <stdio.h>

char line[1024];  /* Generously large value for most situations */

char *eof;

line[0] = '\0'; /* Ensure empty line if no input delivered */
line[sizeof(line)-1] = ~'\0';  /* Ensure no false-null at end of buffer */

eof = fgets(line, sizeof(line), stdin);

Если вы исчерпали ввод консоли или если операция по какой-то причине завершилась неудачно, возвращается eof == NULL и строковый буфер может не измениться (поэтому удобно установить первый символ в '\ 0').

fgets не переполняет строку [] и гарантирует, что после последнего принятого символа при успешном возврате будет ноль.

Если был достигнут конец строки, символ, предшествующий завершающему «\ 0», будет «\ n».

Если нет завершающего символа '\ n' перед окончанием '\ 0', возможно, есть больше данных или что следующий запрос сообщит о конце файла. Вам нужно будет выполнить еще одну операцию, чтобы определить, что есть что. (В этом отношении выполнение цикла с помощью getchar () проще.)

В (обновленном) примере кода выше, если строка [sizeof (line) -1] == '\ 0' после успешных fgets, вы знаете, что буфер был заполнен полностью. Если в этой позиции стоит символ '\ n', значит, вам повезло. В противном случае в stdin впереди либо больше данных, либо конец файла. (Когда буфер не заполнен полностью, вы все еще можете находиться в конце файла, а также может не быть '\ n' в конце текущей строки. Поскольку вам нужно сканировать строку, чтобы найти и / или исключить любой символ '\ n' перед концом строки (первый '\ 0' в буфере), я предпочитаю использовать getchar () в первую очередь.)

Сделайте то, что вам нужно, чтобы иметь дело с тем, что строки все еще больше, чем сумма, которую вы читаете в качестве первого фрагмента. Примеры динамического увеличения буфера можно заставить работать с getchar или fgets. Есть несколько сложных крайних случаев, на которые следует обратить внимание (например, не забыть, что следующий ввод начинается с позиции '\ 0', которая завершила предыдущий ввод до того, как буфер был расширен).

Orcmid
источник
2

Как прочитать строчку из консоли на C?

  • Создание собственной функции - это один из способов, который поможет вам добиться чтения строки из консоли.

  • Я использую динамическое распределение памяти для выделения необходимого объема памяти

  • Когда мы собираемся исчерпать выделенную память, мы пытаемся удвоить размер памяти.

  • И здесь я использую цикл для сканирования каждого символа строки один за другим, используя getchar()функцию, пока пользователь не введет '\n'или EOFсимвол

  • наконец, мы удаляем любую дополнительно выделенную память перед возвратом строки

//the function to read lines of variable length

char* scan_line(char *line)
{
    int ch;             // as getchar() returns `int`
    long capacity = 0;  // capacity of the buffer
    long length = 0;    // maintains the length of the string
    char *temp = NULL;  // use additional pointer to perform allocations in order to avoid memory leaks

    while ( ((ch = getchar()) != '\n') && (ch != EOF) )
    {
        if((length + 1) >= capacity)
        {
            // resetting capacity
            if (capacity == 0)
                capacity = 2; // some initial fixed length 
            else
                capacity *= 2; // double the size

            // try reallocating the memory
            if( (temp = realloc(line, capacity * sizeof(char))) == NULL ) //allocating memory
            {
                printf("ERROR: unsuccessful allocation");
                // return line; or you can exit
                exit(1);
            }

            line = temp;
        }

        line[length] = (char) ch; //type casting `int` to `char`
    }
    line[length + 1] = '\0'; //inserting null character at the end

    // remove additionally allocated memory
    if( (temp = realloc(line, (length + 1) * sizeof(char))) == NULL )
    {
        printf("ERROR: unsuccessful allocation");
        // return line; or you can exit
        exit(1);
    }

    line = temp;
    return line;
}
  • Теперь вы можете прочитать всю строку таким образом:

    char *line = NULL;
    line = scan_line(line);

Вот пример программы, использующей scan_line()функцию:

#include <stdio.h>
#include <stdlib.h> //for dynamic allocation functions

char* scan_line(char *line)
{
    ..........
}

int main(void)
{
    char *a = NULL;

    a = scan_line(a); //function call to scan the line

    printf("%s\n",a); //printing the scanned line

    free(a); //don't forget to free the malloc'd pointer
}

образец ввода:

Twinkle Twinkle little star.... in the sky!

образец вывода:

Twinkle Twinkle little star.... in the sky!
Херувимы
источник
0

Некоторое время назад я столкнулся с той же проблемой, это было мое решение, надеюсь, это поможет.

/*
 * Initial size of the read buffer
 */
#define DEFAULT_BUFFER 1024

/*
 * Standard boolean type definition
 */
typedef enum{ false = 0, true = 1 }bool;

/*
 * Flags errors in pointer returning functions
 */
bool has_err = false;

/*
 * Reads the next line of text from file and returns it.
 * The line must be free()d afterwards.
 *
 * This function will segfault on binary data.
 */
char *readLine(FILE *file){
    char *buffer   = NULL;
    char *tmp_buf  = NULL;
    bool line_read = false;
    int  iteration = 0;
    int  offset    = 0;

    if(file == NULL){
        fprintf(stderr, "readLine: NULL file pointer passed!\n");
        has_err = true;

        return NULL;
    }

    while(!line_read){
        if((tmp_buf = malloc(DEFAULT_BUFFER)) == NULL){
            fprintf(stderr, "readLine: Unable to allocate temporary buffer!\n");
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        if(fgets(tmp_buf, DEFAULT_BUFFER, file) == NULL){
            free(tmp_buf);

            break;
        }

        if(tmp_buf[strlen(tmp_buf) - 1] == '\n') /* we have an end of line */
            line_read = true;

        offset = DEFAULT_BUFFER * (iteration + 1);

        if((buffer = realloc(buffer, offset)) == NULL){
            fprintf(stderr, "readLine: Unable to reallocate buffer!\n");
            free(tmp_buf);
            has_err = true;

            return NULL;
        }

        offset = DEFAULT_BUFFER * iteration - iteration;

        if(memcpy(buffer + offset, tmp_buf, DEFAULT_BUFFER) == NULL){
            fprintf(stderr, "readLine: Cannot copy to buffer\n");
            free(tmp_buf);
            if(buffer != NULL)
                free(buffer);
            has_err = true;

            return NULL;
        }

        free(tmp_buf);
        iteration++;
    }

    return buffer;
}
dsm
источник
1
Ваш код станет НАМНОГО проще, если вы будете использовать его gotoдля обработки случая ошибки. Тем не менее, не думаете ли вы, что вы могли бы использовать его повторно tmp_buf, вместо того, чтобы mallocснова и снова повторять его в цикле?
Shahbaz
Использование одной глобальной переменной has_errдля сообщения об ошибках делает эту функцию небезопасной для потоков и менее удобной в использовании. Не делай так. Вы уже указываете на ошибку, возвращая NULL. Есть также основания полагать, что распечатанные сообщения об ошибках не подходят для универсальной библиотечной функции.
Джонатан Леффлер,
0

В системах BSD и Android вы также можете использовать fgetln:

#include <stdio.h>

char *
fgetln(FILE *stream, size_t *len);

Вот так:

size_t line_len;
const char *line = fgetln(stdin, &line_len);

lineНе нуль и содержит \n(или независимо от вашей платформы используется) , в конце концов. Он становится недействительным после следующей операции ввода-вывода в потоке.

чудо. мышка
источник
Да, функция есть. Предостережение о том, что он не предоставляет строку с завершающим нулем, достаточно велико и проблематично, поэтому, вероятно, лучше не использовать его - это опасно.
Джонатан Леффлер
0

Что-то вроде этого:

unsigned int getConsoleInput(char **pStrBfr) //pass in pointer to char pointer, returns size of buffer
{
    char * strbfr;
    int c;
    unsigned int i;
    i = 0;
    strbfr = (char*)malloc(sizeof(char));
    if(strbfr==NULL) goto error;
    while( (c = getchar()) != '\n' && c != EOF )
    {
        strbfr[i] = (char)c;
        i++;
        strbfr = (void*)realloc((void*)strbfr,sizeof(char)*(i+1));
        //on realloc error, NULL is returned but original buffer is unchanged
        //NOTE: the buffer WILL NOT be NULL terminated since last
        //chracter came from console
        if(strbfr==NULL) goto error;
    }
    strbfr[i] = '\0';
    *pStrBfr = strbfr; //successfully returns pointer to NULL terminated buffer
    return i + 1; 
    error:
    *pStrBfr = strbfr;
    return i + 1;
}

источник
0

Лучший и самый простой способ прочитать строку из консоли - использовать функцию getchar (), с помощью которой вы будете сохранять по одному символу за раз в массиве.

{
char message[N];        /* character array for the message, you can always change the character length */
int i = 0;          /* loop counter */

printf( "Enter a message: " );
message[i] = getchar();    /* get the first character */
while( message[i] != '\n' ){
    message[++i] = getchar(); /* gets the next character */
}

printf( "Entered message is:" );
for( i = 0; i < N; i++ )
    printf( "%c", message[i] );

return ( 0 );

}

Аарон невалинц
источник
-3

Эта функция должна делать то, что вы хотите:

char* readLine( FILE* file )
 {
 char buffer[1024];
 char* result = 0;
 int length = 0;

 while( !feof(file) )
  {
  fgets( buffer, sizeof(buffer), file );
  int len = strlen(buffer);
  buffer[len] = 0;

  length += len;
  char* tmp = (char*)malloc(length+1);
  tmp[0] = 0;

  if( result )
   {
   strcpy( tmp, result );
   free( result );
   result = tmp;
   }

  strcat( result, buffer );

  if( strstr( buffer, "\n" ) break;
  }

 return result;
 }

char* line = readLine( stdin );
/* Use it */
free( line );

Надеюсь, это поможет.

Дэвид Аллан Финч
источник
1
Вы должны делать fgets( buffer, sizeof(buffer), file );не sizeof(buffer)-1. fgetsоставляет место для завершающего нуля.
user102008
Обратите внимание, что while (!feof(file))это всегда неправильно, и это еще один пример ошибочного использования.
Джонатан Леффлер