Wie lese ich eine Zeile von der Konsole in C?

108

Was ist der einfachste Weg, eine vollständige Zeile in einem C-Konsolenprogramm zu lesen? Der eingegebene Text hat möglicherweise eine variable Länge und wir können keine Annahme über den Inhalt treffen.

pbreault
quelle
Könnten Sie bitte klarstellen? Wie @ Tim unten sagte, ist es verwirrend, wonach Sie fragen :)
Warren

Antworten:

81

Sie benötigen eine dynamische Speicherverwaltung und verwenden die fgetsFunktion zum Lesen Ihrer Zeile. Es scheint jedoch keine Möglichkeit zu geben, zu sehen, wie viele Zeichen es liest. Sie verwenden also 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;
}

Hinweis : Niemals verwenden bekommt! Es überprüft keine Grenzen und kann Ihren Puffer überlaufen lassen

Johannes Schaub - litb
quelle
Vorsichtsmaßnahme - muss das Ergebnis der Neuzuweisung dort überprüfen. Wenn dies jedoch fehlschlägt, gibt es höchstwahrscheinlich schlimmere Probleme.
Tim
4
Sie könnten die Effizienz wahrscheinlich ein wenig verbessern, indem Sie Fgets mit Puffer ausführen und prüfen, ob Sie am Ende das Zeilenumbruchzeichen haben. Wenn Sie dies nicht tun, ordnen Sie Ihren Akkumulationspuffer neu zu, kopieren Sie ihn und fgets erneut.
Paul Tomblin
3
Diese Funktion muss korrigiert werden: die Zeile "len = lenmax;" nach dem Realloc sollte entweder vor dem Realloc stehen oder "len = lenmax >> 1;" - oder ein anderes Äquivalent, das die Tatsache berücksichtigt, dass bereits die Hälfte der Länge verwendet wird.
Matt Gallagher
1
@Johannes, als Antwort auf Ihre Frage, @ Pauls Ansatz KÖNNTE bei den meisten (dh wiedereintretenden) libc-Implementierungen schneller sein, da Ihr Ansatz implizit stdin für jedes Zeichen sperrt, während er es einmal pro Puffer sperrt. Sie können das weniger tragbare Gerät verwenden, fgetc_unlockedwenn die Thread-Sicherheit kein Problem darstellt, die Leistung jedoch.
Vladr
3
Beachten Sie, dass sich dies getline()von der POSIX-Standardfunktion unterscheidet getline().
Jonathan Leffler
28

Wenn Sie die GNU C-Bibliothek oder eine andere POSIX-kompatible Bibliothek verwenden, können Sie sie für den Dateistream verwenden getline()und stdinan sie übergeben.

dmityugov
quelle
16

Eine sehr einfache, aber unsichere Implementierung zum Lesen der Zeile für die statische Zuordnung:

char line[1024];

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

Eine sicherere Implementierung ohne die Möglichkeit eines Pufferüberlaufs, aber mit der Möglichkeit, nicht die gesamte Zeile zu lesen, ist:

char line[1024];

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

Nicht die 'Differenz um eins' zwischen der angegebenen Länge, die die Variable deklariert, und der in der Formatzeichenfolge angegebenen Länge. Es ist ein historisches Artefakt.

Jonathan Leffler
quelle
13
Das ist überhaupt nicht sicher. Es leidet unter genau dem gleichen Problem, warum getsinsgesamt aus dem Standard entfernt wurde
Antti Haapala
5
Anmerkung des Moderators: Der obige Kommentar bezieht sich auf eine frühere Überarbeitung der Antwort.
Robert Harvey
13

Wenn Sie also nach Befehlsargumenten suchen, werfen Sie einen Blick auf Tims Antwort. Wenn Sie nur eine Zeile von der Konsole lesen möchten:

#include <stdio.h>

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

Ja, es ist nicht sicher, Sie können einen Pufferüberlauf durchführen, es wird nicht nach Dateiende gesucht, es werden keine Codierungen und viele andere Dinge unterstützt. Eigentlich habe ich nicht einmal darüber nachgedacht, ob es irgendetwas davon getan hat. Ich bin damit einverstanden, dass ich es irgendwie vermasselt habe :) Aber ... wenn ich eine Frage wie "Wie lese ich eine Zeile von der Konsole in C?" Sehe, gehe ich davon aus, dass eine Person etwas Einfaches braucht, wie get () und nicht 100 Codezeilen wie oben. Eigentlich denke ich, wenn Sie versuchen, diese 100 Codezeilen in der Realität zu schreiben, würden Sie viel mehr Fehler machen, als Sie getan hätten, wenn Sie sich dafür entschieden hätten;)

Paul Kapustin
quelle
1
Dies erlaubt keine langen Saiten ... - was meiner Meinung nach der Kern seiner Frage ist.
Tim
2
-1, get () sollte nicht verwendet werden, da keine Grenzüberprüfung durchgeführt wird.
Entspannen Sie sich
7
Wenn Sie jedoch ein Programm für sich selbst schreiben und nur eine Eingabe lesen müssen, ist dies vollkommen in Ordnung. Wie viel Sicherheit ein Programm benötigt, hängt von der Spezifikation ab - Sie MÜSSEN es nicht jedes Mal als Priorität festlegen.
Martin Beckett
4
@ Tim - Ich möchte die ganze Geschichte behalten :)
Paul Kapustin
4
Abgestimmt. getsexistiert nicht mehr, daher funktioniert dies in C11 nicht.
Antti Haapala
11

Möglicherweise müssen Sie eine zeichenweise (getc ()) - Schleife verwenden, um sicherzustellen, dass keine Pufferüberläufe auftreten und die Eingabe nicht abgeschnitten wird.

Tim
quelle
9

getline lauffähiges Beispiel

Erwähnt zu dieser Antwort, aber hier ist ein Beispiel.

Es ist POSIX 7, weist uns Speicher zu und verwendet den zugewiesenen Puffer in einer Schleife gut wieder.

Zeiger newbs, lesen Sie Folgendes: Warum ist das erste Argument von getline ein Zeiger auf den Zeiger "char **" anstelle von "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 Implementierung

Kein POSIX? Vielleicht möchten Sie sich die Implementierung von glibc 2.23 ansehen .

Es wird aufgelöst in getdelim, was eine einfache POSIX-Obermenge von getlinemit einem beliebigen Zeilenabschluss ist.

Es verdoppelt den zugewiesenen Speicher, wenn eine Erhöhung erforderlich ist, und sieht threadsicher aus.

Es erfordert eine gewisse Makroerweiterung, aber es ist unwahrscheinlich, dass Sie es besser machen.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
Was ist der Zweck von lenhier, wenn das Lesen auch die Länge liefert
Abdul
@Abdul sehen man getline. lenist die Länge des vorhandenen Puffers, 0ist magisch und weist ihn an, zuzuweisen. Lesen ist die Anzahl der gelesenen Zeichen. Die Puffergröße könnte größer sein als read.
Ciro Santilli 31 冠状 病 六四 事件 31
6

Viele Leute, wie ich, kommen zu diesem Beitrag mit dem Titel, der dem entspricht, wonach gesucht wird, obwohl die Beschreibung über variable Länge sagt. In den meisten Fällen kennen wir die Länge im Voraus.

Wenn Sie die Länge vorher kennen, versuchen Sie es unten:

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

Quelle: https://www.tutorialspoint.com/c_standard_library/c_function_fgets.htm

Manohar Reddy Poreddy
quelle
5

Wie vorgeschlagen, können Sie mit getchar () von der Konsole lesen, bis ein Zeilenende oder ein EOF zurückgegeben wird, und Ihren eigenen Puffer erstellen. Das dynamische Wachsen des Puffers kann auftreten, wenn Sie keine angemessene maximale Zeilengröße festlegen können.

Sie können auch fgets verwenden, um eine Zeile als C-nullterminierte Zeichenfolge auf sichere Weise abzurufen:

#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);

Wenn Sie die Konsoleneingabe erschöpft haben oder der Vorgang aus irgendeinem Grund fehlgeschlagen ist, wird eof == NULL zurückgegeben und der Zeilenpuffer bleibt möglicherweise unverändert (weshalb es praktisch ist, das erste Zeichen auf '\ 0' zu setzen).

fgets überfüllt die Zeile [] nicht und stellt sicher, dass bei einer erfolgreichen Rückgabe nach dem zuletzt akzeptierten Zeichen eine Null steht.

Wenn das Zeilenende erreicht wurde, ist das Zeichen vor dem abschließenden '\ 0' ein '\ n'.

Wenn vor dem Ende von '\ 0' kein '\ n' beendet wird, kann es sein, dass mehr Daten vorhanden sind oder dass die nächste Anforderung das Dateiende meldet. Sie müssen weitere Fgets ausführen, um festzustellen, welches welches ist. (In dieser Hinsicht ist das Schleifen mit getchar () einfacher.)

Im obigen (aktualisierten) Beispielcode wissen Sie, dass der Puffer vollständig gefüllt wurde, wenn Zeile [sizeof (Zeile) -1] == '\ 0' nach erfolgreichen Fgets ist. Wenn diese Position von einem '\ n' besetzt wird, wissen Sie, dass Sie Glück hatten. Andernfalls liegen in stdin entweder mehr Daten oder ein Dateiende vor uns. (Wenn der Puffer nicht vollständig gefüllt ist, befindet sich möglicherweise immer noch ein Dateiende und am Ende der aktuellen Zeile befindet sich möglicherweise kein '\ n'. Da Sie die Zeichenfolge scannen müssen, um und / oder zu finden oder ein '\ n' vor dem Ende des Strings entfernen (das erste '\ 0' im Puffer), ich bin geneigt, getchar () überhaupt zu verwenden.)

Tun Sie, was Sie tun müssen, um damit umzugehen, dass immer noch mehr Zeilen als der Betrag vorhanden sind, den Sie als ersten Block gelesen haben. Die Beispiele für das dynamische Wachsen eines Puffers können entweder mit getchar oder fgets verwendet werden. Es gibt einige knifflige Randfälle, auf die Sie achten müssen (z. B. das Erinnern daran, dass der nächste Eingang an der Position '\ 0' gespeichert wird, die den vorherigen Eingang beendet hat, bevor der Puffer erweitert wurde).

orcmid
quelle
2

Wie lese ich eine Zeile von der Konsole in C?

  • Das Erstellen einer eigenen Funktion ist eine der Möglichkeiten, mit denen Sie eine Zeile von der Konsole aus lesen können

  • Ich verwende die dynamische Speicherzuweisung , um die erforderliche Speichermenge zuzuweisen

  • Wenn wir den zugewiesenen Speicher erschöpfen wollen, versuchen wir, die Größe des Speichers zu verdoppeln

  • Und hier bin ich mit einer Schleife jedes Zeichen der Zeichenfolge ein scannen , indem man mit der getchar()Funktion , bis der Benutzer eingibt '\n'oder EOFZeichen

  • Schließlich entfernen wir den zusätzlich zugewiesenen Speicher, bevor wir die Zeile zurückgeben

//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;
}
  • Jetzt können Sie eine vollständige Zeile folgendermaßen lesen:

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

Hier ist ein Beispielprogramm mit der scan_line()Funktion:

#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
}

Beispieleingabe:

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

Beispielausgabe:

Twinkle Twinkle little star.... in the sky!
Cherubim
quelle
0

Ich bin vor einiger Zeit auf dasselbe Problem gestoßen, dies war meine Lösung, ich hoffe, es hilft.

/*
 * 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
quelle
1
Ihr Code würde VIEL einfacher, wenn Sie gotoden Fehlerfall behandeln. Glauben Sie trotzdem nicht, dass Sie es wiederverwenden könnten tmp_buf, anstatt malloces immer wieder mit der gleichen Größe in der Schleife zu verwenden?
Shahbaz
Die Verwendung einer einzelnen globalen Variablen has_errzum Melden von Fehlern macht diese Funktion threadsicher und weniger benutzerfreundlich. Mach es nicht so. Sie geben bereits einen Fehler an, indem Sie NULL zurückgeben. Es gibt auch Raum zu der Annahme, dass die gedruckten Fehlermeldungen in einer universellen Bibliotheksfunktion keine gute Idee sind.
Jonathan Leffler
0

Auf BSD-Systemen und Android können Sie außerdem Folgendes verwenden fgetln:

#include <stdio.h>

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

Wie so:

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

Das lineist nicht nullterminiert und enthält \n(oder was auch immer Ihre Plattform verwendet) am Ende. Sie wird nach der nächsten E / A-Operation im Stream ungültig.

Wonder.mice
quelle
Ja, die Funktion ist vorhanden. Die Einschränkung, dass keine nullterminierte Zeichenfolge bereitgestellt wird, ist ausreichend groß und problematisch, sodass es wahrscheinlich besser ist, sie nicht zu verwenden - sie ist gefährlich.
Jonathan Leffler
0

Etwas wie das:

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;
}

quelle
0

Der beste und einfachste Weg, eine Zeile von einer Konsole zu lesen, ist die Funktion getchar (), mit der Sie jeweils ein Zeichen in einem Array speichern.

{
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 );

}}

Aaron nevalinz
quelle
-3

Diese Funktion sollte tun, was Sie wollen:

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 );

Ich hoffe das hilft.

David Allan Finch
quelle
1
Das solltest du fgets( buffer, sizeof(buffer), file );nicht sizeof(buffer)-1. fgetslässt Platz für die abschließende Null.
user102008
Beachten Sie, dass dies while (!feof(file))immer falsch ist und dies nur ein weiteres Beispiel für eine fehlerhafte Verwendung ist.
Jonathan Leffler