Wie liest man eine Zeile von der Konsole in C?

Lesezeit: 7 Minuten

Benutzeravatar von pbreault
pbreault

Was ist der einfachste Weg, eine ganze Zeile in einem C-Konsolenprogramm zu lesen? Der eingegebene Text kann eine variable Länge haben, und wir können keine Annahme über seinen Inhalt machen.

Johannes Schaub - Benutzerbild der litb
Johannes Schaub – litb

Sie benötigen eine dynamische Speicherverwaltung und verwenden die fgets Funktion, um Ihre Zeile zu lesen. Es scheint jedoch keine Möglichkeit zu geben, zu sehen, wie viele Zeichen es gelesen hat. 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;
}

Notiz: Benutze niemals gets ! Es führt keine Begrenzungsprüfung durch und kann Ihren Puffer überlaufen lassen

  • Vorbehalt – Sie müssen das Ergebnis von realloc dort überprüfen. Aber wenn das fehlschlägt, dann gibt es höchstwahrscheinlich schlimmere Probleme.

    – Tim

    24. November 2008 um 15:24 Uhr

  • Sie könnten die Effizienz wahrscheinlich etwas 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 hinein und fgets erneut.

    – Paul Tomblin

    24. November 2008 um 15:27 Uhr

  • Diese Funktion benötigt eine Korrektur: Die Zeile “len = lenmax;” nach dem realloc sollte entweder dem realloc vorangestellt werden oder es sollte “len = lenmax >> 1” stehen; — oder ein anderes Äquivalent, das die Tatsache berücksichtigt, dass die Hälfte der Länge bereits verwendet wird.

    – Matt Gallagher

    26. Juli 2010 um 7:41 Uhr

  • @Johannes, als Antwort auf Ihre Frage könnte der Ansatz von @Paul bei den meisten (dh wiedereintrittsfähigen) libc-Implementierungen schneller sein, da Ihr Ansatz stdin implizit für jedes Zeichen sperrt, während sein Ansatz es einmal pro Puffer sperrt. Sie könnten das weniger tragbare verwenden fgetc_unlocked wenn die Thread-Sicherheit keine Rolle spielt, aber die Leistung.

    – vladr

    10. April 2012 um 16:02 Uhr

  • Beachten Sie, dass dies getline() unterscheidet sich vom POSIX-Standard getline() Funktion.

    – Jonathan Leffler

    12. April 2017 um 5:42 Uhr

Benutzeravatar von dmityugov
dmitjugow

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

Eine sehr einfache, aber unsichere Implementierung zum Lesen von Zeilen für die statische Zuweisung:

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 bei der Variablendeklaration angegebenen Länge und der im Formatstring angegebenen Länge. Es ist ein historisches Artefakt.

Benutzeravatar von Paul Kapustin
Paul Kapustin

Wenn Sie also nach Befehlsargumenten gesucht haben, 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 prüft nicht auf das Dateiende, es unterstützt keine Codierungen und viele andere Dinge. Eigentlich habe ich nicht einmal darüber nachgedacht, ob es irgendetwas von diesem Zeug tat. Ich stimme zu, dass ich es irgendwie vermasselt habe 🙂 Aber … wenn ich eine Frage wie “Wie liest man eine Zeile von der Konsole in C?” sehe, nehme ich an, dass eine Person etwas Einfaches braucht, wie gets() und nicht 100 Zeilen Code 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 entschieden hätten 😉

Ciro Santilli Benutzeravatar von OurBigBook.com
Ciro Santilli OurBigBook.com

getline lauffähiges Beispiel

getline wurde in dieser Antwort erwähnt, aber hier ist ein Beispiel.

es ist Posix 7weist uns Speicher zu und verwendet den zugewiesenen Puffer in einer Schleife gut wieder.

Zeiger-Neulinge, lesen Sie dies: Warum ist das erste Argument von getline ein Zeiger auf den Zeiger “char**” anstelle von “char*”?

Haupt c

#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 (1) {
        puts("enter a line");
        read = getline(&line, &len, stdin);
        if (read == -1)
            break;
        printf("line = %s", line);
        printf("line length = %zu\n", read);
        puts("");
    }
    free(line);
    return 0;
}

Kompilieren und ausführen:

gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out

Ergebnis: Dies zeigt auf Thermal:

enter a line

Wenn Sie dann eingeben:

asdf

und Enter drücken, das erscheint:

line = asdf
line length = 5

gefolgt von einem anderen:

enter a line

Oder von einer Pipe zu stdin:

printf 'asdf\nqwer\n' | ./main.out

gibt:

enter a line
line = asdf
line length = 5

enter a line
line = qwer
line length = 5

enter a line

Getestet auf Ubuntu 20.04.

glibc-Implementierung

Kein POSIX? Vielleicht möchten Sie sich die anschauen glibc 2.23-Implementierung.

Es beschließt getdelimwas eine einfache POSIX-Obermenge von ist getline mit einem beliebigen Zeilenabschluss.

Es verdoppelt den zugewiesenen Speicher, wann immer eine Erhöhung erforderlich ist, und sieht Thread-sicher aus.

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

  • Was ist der Zweck von len hier, wenn gelesen, liefert auch die Länge

    – Honinbo Shusaku

    31. März 2017 um 19:32 Uhr

  • @ Abdul siehe man getline. len ist die Länge des vorhandenen Puffers, 0 ist magisch und sagt ihm, es zuzuweisen. Read ist die Anzahl der gelesenen Zeichen. Die Puffergröße könnte größer sein als read.

    – Ciro Santilli OurBigBook.com

    31. März 2017 um 19:40 Uhr

Benutzeravatar von Tim
Tim

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

  • Was ist der Zweck von len hier, wenn gelesen, liefert auch die Länge

    – Honinbo Shusaku

    31. März 2017 um 19:32 Uhr

  • @ Abdul siehe man getline. len ist die Länge des vorhandenen Puffers, 0 ist magisch und sagt ihm, es zuzuweisen. Read ist die Anzahl der gelesenen Zeichen. Die Puffergröße könnte größer sein als read.

    – Ciro Santilli OurBigBook.com

    31. März 2017 um 19:40 Uhr

Wie vorgeschlagen, können Sie getchar() verwenden, um von der Konsole zu lesen, bis ein Zeilenende oder ein EOF zurückgegeben wird, wodurch Sie Ihren eigenen Puffer erstellen. Ein dynamisch wachsender Puffer kann auftreten, wenn Sie keine vernünftige maximale Zeilengröße festlegen können.

Sie können auch fgets als sicheren Weg verwenden, um eine Zeile als nullterminierten C-String zu erhalten:

#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 die Operation aus irgendeinem Grund fehlgeschlagen ist, wird eof == NULL zurückgegeben und der Zeilenpuffer könnte unverändert bleiben (weshalb es praktisch ist, das erste Zeichen auf ‘\0’ zu setzen).

fgets wird die Zeile nicht überfüllen[] und es wird sichergestellt, 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 kein abschließendes ‘\n’ vor der Endung ‘\0’ steht, kann es sein, dass mehr Daten vorhanden sind oder dass die nächste Anfrage das Dateiende meldet. Sie müssen weitere fgets durchführen, um festzustellen, welches was ist. (In dieser Hinsicht ist das Schleifen mit getchar() einfacher.)

Im obigen (aktualisierten) Beispielcode if line[sizeof(line)-1] == ‘\0’ nach erfolgreichem fgets wissen Sie, dass der Puffer vollständig gefüllt wurde. Wenn dieser Position ein ‘\n’ vorangeht, wissen Sie, dass Sie Glück hatten. Andernfalls gibt es entweder mehr Daten oder ein Dateiende vorn in stdin. (Wenn der Puffer nicht vollständig gefüllt ist, befinden Sie sich möglicherweise immer noch am Dateiende und am Ende der aktuellen Zeile steht möglicherweise kein ‘\n’. Da Sie die Zeichenfolge scannen müssen, um zu finden und/ oder jedes ‘\n’ vor dem Ende der Zeichenfolge entfernen (das erste ‘\0’ im Puffer), neige ich dazu, zuerst getchar() zu verwenden.)

Tun Sie, was Sie tun müssen, um damit fertig zu werden, dass immer noch mehr Linien vorhanden sind als die Menge, die Sie als ersten Block gelesen haben. Die Beispiele für das dynamische Wachsen eines Puffers können entweder mit getchar oder fgets zum Laufen gebracht werden. Es gibt einige knifflige Grenzfälle, auf die Sie achten müssen (z. B. daran zu denken, dass die nächste Eingabe an der Position des ‘\0’ zu speichern beginnt, die die vorherige Eingabe beendet hat, bevor der Puffer erweitert wurde).

1422720cookie-checkWie liest man eine Zeile von der Konsole in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy