staging.inyokaproject.org

Darf man strtok() noch mit gutem Gewissen verwenden? (C/C++)

Status: Gelöst | Ubuntu-Version: Ubuntu MATE 20.04 (Focal Fossa)
Antworten |

Dakuan

Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6234

Bisher hatte ich die Funktion strtok() verwendet, um die Antwort eines Servers (Verzeichnis Abfrage) auszuwerten. Die Antwort kommt in mehreren Spalten (text/plain) und sieht normalerweise so aus:

...
1654939277      99001980        1669822704      1664029910      video.mp4
...

Die Spalten sind durch [TAB] getrennt. Die Spalten sind:

  • video mtime

  • video size

  • thumbnail file mtime (file system)

  • thumbnail file mtime (stored in file)

  • Dateiname

Die 4-te Spalte ist dabei etwas problematisch, da dafür die Vorschaudatei analysiert werden muss, was etwas dauert. Daher möchte ich eine "schnelle Abfrage" ermöglichen, in der diese Felder leer sind. Bisher hat das immer funktioniert, da die Spalten nicht leer waren. Die Spalten könnten könnten natürlich auch aus anderen Gründen leer sein.

Aber wenn die Spalten leer sind, stürzt mein Programm ab! Offensichtlich werden bei leeren Feldern alle Delimiter übersprungen anstatt nur der nächste.

... From the above description, it follows that a sequence of two or more contiguous delimiter bytes in the parsed string is considered to be a single delimiter, ...

Das ist natürlich blöd, bzw. in meinem Fall nicht brauchbar. Gibt es da modernere Alternativen oder muss ich da selber etwas erfinden?

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 12527

Dakuan schrieb:

Das ist natürlich blöd, bzw. in meinem Fall nicht brauchbar. Gibt es da modernere Alternativen oder muss ich da selber etwas erfinden?

Was ist denn die Frage? Soll es ein anderes Format sein oder ist das fix? Suchst Du nach einer anderen Möglichkeit, das Format zu parsen? Wenn es wirklich nur Tabs als Trenner sind, dann kann man das ja auch recht einfach selbst programmieren.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6234

Es geht mir darum, dass diese Funktion wahrscheinlich nicht wirklich die die einzelnen Spalten trennt, so wie man es beispielsweise von CSV Daten erwarten würde. Ob man jetzt ein Komma oder ein TAB als Trenner verwendet macht ja nicht viel Unterschied.

Jedenfalls sind bei CSV Daten leere Spalten erlaubt.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 12527

Dakuan schrieb:

Es geht mir darum, dass diese Funktion wahrscheinlich nicht wirklich die die einzelnen Spalten trennt, so wie man es beispielsweise von CSV Daten erwarten würde. Ob man jetzt ein Komma oder ein TAB als Trenner verwendet macht ja nicht viel Unterschied.

Jedenfalls sind bei CSV Daten leere Spalten erlaubt.

Der Sinn Deiner Worte ist dunkel. Was ist Dein Begehr?

frostschutz

Avatar von frostschutz

Anmeldungsdatum:
18. November 2010

Beiträge: 7529

versuch dein Glück mit strsep

EXAMPLES
       The program below is a port of the one found in strtok(3), which, however,
       doesn't discard multiple delimiters or empty tokens:

           $ ./a.out 'a/bbb///cc;xxx:yyy:' ':;' '/'
           1: a/bbb///cc
                    --> a
                    --> bbb
                    -->
                    -->
                    --> cc
           2: xxx
                    --> xxx
           3: yyy
                    --> yyy
           4:
                    -->

Allerdings modifiziert strsep den String mit dem es arbeitet, wenn das wichtig ist eben auf einer Kopie...

Hmmm diese Änderung der Manpage scheint noch nicht weit verbreitet zu sein - die alte Version hatte das Beispiel noch nicht

https://man.archlinux.org/man/strsep.3.en

https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/man3/strsep.3

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6234

Die Funktion kannte ich noch nicht und sie fehlt in meinen Büchern.

Allerdings modifiziert strsep den String mit dem es arbeitet,...

Das ist kein Problem, da es sich um einen Empfangspuffer handelt. Der wird nach Auswertung entsorgt.

Ich werde das heute Abend mal ausprobieren. Die Beschreibung klingt aber so, als hätte ich die Funktion schon für die Serverseite zum extrahieren der Get Parameter gebrauchen können.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6234

Ich habe jetzt mein Programm auf strsep() umgestellt. Das funktioniert prima. Das gezeigte Beispiel ist auch interessant. Ich wäre nie auf die Idee mit dem subtoken gekommen.

Aber da strsep() nicht überall implementiert ist, muss man wohl doch manchmal auf strtok() zurückgreifen und ggf. einige Klimmzüge machen. Für meine Projekte ist das aber kein Thema.

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2119

Was für Klimmzüge? Die Implementierung in der glibc sieht so aus:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
char *
__strsep (char **stringp, const char *delim)
{
  char *begin, *end;
  begin = *stringp;
  if (begin == NULL)
    return NULL;
  /* Find the end of the token.  */
  end = begin + strcspn (begin, delim);
  if (*end)
    {
      /* Terminate the token and set *STRINGP past NUL character.  */
      *end++ = '\0';
      *stringp = end;
    }
  else
    /* No more delimiters; this is the last token.  */
    *stringp = NULL;
  return begin;
}

Das ist also relativ simpel aufgebaut, Quelle: https://codebrowser.dev/glibc/glibc/string/strsep.c.html

Denkbar ist auch ein Nachbau mit strchr() oder strpbrk(). Oder zur Not ein manuelles Durchlaufen der Bytes.

Ob du dann Teilstrings kopierst oder den Ansatz mit dem Wiederverwenden des Pointers durch Ersetzen der Trenner mit Null-Bytes verfolgst, musst du selber wissen.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6234

Was für Klimmzüge?

Da hatte ich es wohl wieder mal zu eilig. Das mit den Klimmzügen bezog sich auf den ersten, von frostschutz geposteten Link und auf den Hinweis darin, wie das gleiche Problem ursprünglich mit strtok() bzw. mit strtok_r() gelöst wurde (Beispiel ganz unten). Der Code ist etwa doppelt so lang.

Ich hatte nicht daran gedacht, die Funktion nachzubauen, wenn sie fehlt. In einem Forumsbeitrag, den ich gestern noch gefunden hatte, wurde das allerdings vorgeschlagen.

Als Trennzeichen habe ich jetzt den "/" Schrägstrich gewählt, da dieser in Dateinamen nicht vorkommen kann.

Neral

Anmeldungsdatum:
3. Oktober 2007

Beiträge: Zähle...

Man könnte auch einfach was fertiges nehmen, statt sich da selbst was zusammenzubasteln:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
#include <vector>
#include <ranges>
#include <string_view>

#include <fmt/core.h>
#include <fmt/ranges.h>

#include <range/v3/all.hpp>

using std::literals::operator""sv;

auto main() -> int {
    constexpr auto line = "a\tb\t\td"sv;
    auto fields = line | std::views::split('\t') | ranges::to<std::vector<std::string_view>>;
    fmt::println("{}", fields);
}

(Benutzt range-v3 (weil std::ranges::to in GCC noch nicht implementiert ist) und {fmt} (weil std::println noch nicht implementiert ist).)

Ausgabe:

["a", "b", "", "d"]

Aber die eigentliche Frage ist, warum das Format so ist. Normalerweise will man gut spezifizierte Formate benutzen, für die es robuste Bibliotheken zum Erstellen und zum Parsen gibt. JSON zum Beispiel.

Dakuan

(Themenstarter)
Avatar von Dakuan

Anmeldungsdatum:
2. November 2004

Beiträge: 6234

Aber die eigentliche Frage ist, warum das Format so ist.

Weil es einfach ist und schnell zu realisieren war.
Also das ist mein erster Versuch zur Netzwerkprogrammierung und wahrscheinlich nicht der letzte. Der Server liefert übrigens (fast) nur folgende Daten aus:

Content-Type: text/plain; charset="utf-8"
Content-Type: image/png

und bei Direktory Abfragen anfangs nur die Dateinamen. Die anderen Werte sind später hinzugekommen um unnötige Statusabfragen zu reduzieren. Das es jetzt auch leere Felder geben können soll, hängt damit zusammen, dass für das letzte Zusatzfeld, die zugehörige Vorschaudatei geöffnet und durchsucht werden muss. Das kann ggf. etwas dauern.

Und das TAB Zeichen als Trenner hatte ich ursprünglich gewählt, weil ich die Funktion des Servers zuerst mit Dillo getestet hatte. Das ließt sich dann besser.

Antworten |