ifstream::open - Längenbeschränkung für Dateinamen?



  • Hi(gh)!

    Ich stelle fest, dass ab Januar 2020, wo die Zahl der gespeicherten eBay-Orgel-Anzeigen pro Monat merklich zunimmt, von meinem Programm nur noch ein Teil (rund zwei Drittel) überhaupt geöffnet werden. Könnte es sein, dass manche Dateinamen schlicht zu lang für die Methode "open" sind?

    Bis bald im Khyberspace!

    Yadgar



  • Kommt auf das (Betriebs&Datei)System an.
    Windows hat beschränkungen für die Länge des Pfads (MAX_PATH). Es gibt ein Trick der unter umständen funktioniert, wo ich leider vergessen habe wieder der genau aussah, irgendwas mit führendem \\?\.

    Hier findet man alles was man wissen muss:
    https://learn.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=registry

    Linux: Bin nie in solch ein Problem gerannt. ext4 hat ein Dateinamenlimit von 255 Zeichen. Für pfadlängen gibt es für das Dateisystem ext4 gar kein Limit, aber man findet 4k als Limit wenn man googelt was irgendwo definiert ist in einem header. welche Linux/Gnu Funktionen das enforcen ist aber bestimmt schwierig zu sagen.



  • Linux: Bin nie in solch ein Problem gerannt. ext4 hat ein Dateinamenlimit von 255 Zeichen. Für pfadlängen gibt es für das Dateisystem ext4 gar kein Limit, aber man findet 4k als Limit wenn man googelt was irgendwo definiert ist in einem header. welche Linux/Gnu Funktionen das enforcen ist aber bestimmt schwierig zu sagen.

    Jetzt kann ich im Moment leider nicht sagen, ob ich ext4 oder doch noch ext3 habe... die längsten Dateinamen in meinem Verzeichnis sind so um die 180 Zeichen lang! Woran könnte es denn sonst noch liegen? Hier ist der Code:

    /* oac (Organ Ads Compressor) 
     Programm zur Reduzierung von archivierten eBay-Elektroorgel-Anzeigen auf das zur Erfassung in Datenbanken erforderliche Minimum */
    
    // DANKSAGUNGEN:
    // Lasha Khintibidze für Hilfe beim Erzeugen von Verzeichnissen mittels create_directory (https://www.delftstack.com/de/howto/cpp/cpp-create-directory/)
    // "Finnegan" vom C++-Community-Forum für den Hinweis auf -std=c++17 als zur fehlerfreien Einbindung von <filesystem> notwendigen gcc-Compileroption
    
    #include <iostream>
    #include <fstream>
    #include <string>
    #include <vector>
    #include <dirent.h>
    #include <filesystem>
    #include <iterator>
    #include <map>
    #include "oac.conf"
    using namespace std;
    namespace fs = std::filesystem;
    
    
    
    int main(int argc, char* argv[])
    {
      DIR *dir, *subdir;
      dirent *entry;
    
    #ifdef MONAT
      string lastdir=string(argv[1]) + "/";
    #endif
    #ifdef TEST
      string lastdir="test/";
    #endif  
      
      string altpfad="/home/Musik/Sonstiges/WWW-Seiten, gescannte Texte/Orgeln/eBay-Kleinanzeigen/" + lastdir;
      string neupfad="/home/Musik/Sonstiges/WWW-Seiten, gescannte Texte/Orgeln/eBay-Kleinanzeigen_neu/" + lastdir;
      char *name; // Dateinamen
      ifstream datei, bilddatei;
      ofstream datei_neu;
      string source; // eingelesene Original-HTML-Textdatei als String
      string beschreibung; // Anzeigentext, zu finden nach 'itemprop="description">'
      string benutzer; // Benutzername, zu finden nach '<div class="usercard--info--collumn">'
      string ort; // Postleitzahl und Ort, zu finden nach 'itemprop="locality">'
      string erstdatum; // Erstellungsdatum, zu finden nach "Erstellungsdatum"
      string preis; // Preis, zu finden nach "Preis:"
      string filedate; // Anzeigendatum als Namens- und Titelbestandteil für zu erzeugende neue HTML-Datei
      int occ, occ2, occ3; // Variable für Stringpositionen
      char c; // eingelesenes Zeichen aus Original-HTML-Textdatei oder Datenbyte aus Bilddatei
      string dn; // Anzahl der erfassten Anzeigen eines Datums als String (Teil des Dateinamens und Titels der zu erzeugenden neuen HTML-Datei)
      string pn; // Bilddatei-Nummer als String
      /* Array, Zähler und Ergebnisvariable für Suche nach Ziffern - ich bin leider zu dumm, um die Implementation regulärer Ausdrücke in <regex> zu verstehen! */
      unsigned int pw[10]; // Array für Suchergebnisse für die Ziffern 0 bis 9
      unsigned short int i;
      unsigned int pos;
      map<string, int> Daten; // "Array" für Datums-Strings und deren Häufigkeiten
      unsigned int ad=0; // Anzeigen-Zähler
    
      dir = opendir(altpfad.c_str());
      while (dir)
      {
        entry = readdir(dir);
        if (!entry) break;
        name = entry->d_name;
      
        // cout << name << endl; // Testausgabe des Dateinamens
        string vollpfad_alt = altpfad;
        vollpfad_alt.append(string(name));
        // cout << vollpfad_alt << endl;
        if (vollpfad_alt.find("eBay Kleinanzeigen.html") != -1) // nur eBay-Kleinanzeigen öffnen (keine versehentlich abgespeicherten eBay-Auktionen)!
        {
          datei.open(vollpfad_alt.c_str(), ios_base::in);
          string pfad_alt = altpfad;    
          if (datei)
          {
            while (datei.get(c))
            {
              source.push_back(c); // html-Datei wird eingelesen
            }
          }
          else
          {
            cerr << "Die Datei kann nicht geöffnet werden!" << endl;
          }
          
          datei.close();
          
          // Auslesen der Beschreibung
          occ = source.find("itemprop=\"description\">");
          occ2 = source.find ("</p>", occ);
          if (occ == -1 || occ2 == -1)
          {
            cerr << "Beschreibung kann nicht gefunden werden!" << endl;
    
            return -1;
          }
          beschreibung = source.substr(occ+23, occ2-(occ+23));
    
          // Auslesen des Benutzernamens
          occ = source.find("<div class=\"usercard--info--collumn\">");
          occ = source.find("<h2>", occ);
          occ2 = source.find("</h2>", occ);
          if (occ == -1 || occ2 == -1)
          {
            cerr << "Benutzername kann nicht gefunden werden!" << endl;
            cout << vollpfad_alt << endl;
            return -1;
          }
          benutzer = source.substr(occ+4, occ2-(occ+4));
    
          // Auslesen der Ortsinformationen
          occ = source.find("itemprop=\"locality\">");
          if (occ == -1)
          {
            cerr << "Ortsinformationen können nicht gefunden werden!" << endl;
            cout << vollpfad_alt << endl;
            return -1;
          }
          for (i=0; i<10; i++) // Suche nach Postleitzahl
          {
            pw[i] = source.find((char)'0'+i, occ);
            if (i == 0)
              pos = pw[i];
            else
              if (pw[i] < pos)
                pos = pw[i];
          }
          if (pw[i] == -1)
          {
            cerr << "Postleitzahl kann nicht gefunden werden!" << endl;
            cout << vollpfad_alt << endl;
            return -1;
          }
          occ = source.find("</span>", pos);
          if (pw[1] == -1)
          {
              cerr << "Ende der Ortsinformationen kann nicht gefunden werden!" << endl;
              cout << vollpfad_alt << endl;
              return -1;
          }
          ort = source.substr(pos, occ-pos);
    
          
          
          // Auslesen des Erstellungsdatums
          occ = source.find("Erstellungsdatum");
          occ2 = source.find("icon-calendar-gray-simple"); // Datums-Icon ab 31. Januar 2020
          if ( occ == -1 && occ2 == -1)
          {
            cerr << "Erstellungsdatum kann nicht gefunden werden!" << endl;
            cout << vollpfad_alt << endl;
            return 1;
          }
          else
          {
            if (occ2 > -1)
              occ = occ2;
            for (i=0; i<10; i++)
            {
              pw[i] = source.find((char)'0'+i, occ);
              if (i == 0)
                pos = pw[i];
              else
                if (pw[i] < pos)
                  pos = pw[i];
            }
          }
          if (pw[i] == -1)
          {
            cerr << "Erstellungsdatum kann nicht gefunden werden!" << endl;
            cout << vollpfad_alt << endl;
            return 1;
          }
          erstdatum = source.substr(pos, 10);
          
          
          
          // Auslesen des Preises
          occ = source.find("category: 'Zu verschenken'");
          occ2 = source.find("category: 'Verschenken'"); // neuer Kategorienname ab Juli 2019!
          if (occ > -1 || occ2 > -1 )
            preis = "0 € (zu verschenken)";
          else
          {
            occ = source.find("category: 'Tauschen'");
            if (occ > -1)
              preis = "Zu tauschen";
            else
            {
              occ = source.find("Preis:");
              occ2 = source.find("Zu verschenken", occ);
              if (occ2 != -1)
                preis = "0 € (zu verschenken)";
              else
              {  
                if (source[occ+7] < '0' || source[occ+7] > '9') // falls kein Zahlenwert gefunden wird
                  preis = "VB";
                else  
                {  
                  occ3 = source.find("</h2>", occ);
                  if (occ3 == -1)
                  {
                    cerr << "Preisangaben können nicht gefunden werden!" << endl;
                    cout << vollpfad_alt << endl;
                    return -1;
                  }  
                  preis = source.substr(occ+7, occ3-(occ+7));
                }
              }
            }  
          }
          
          source.clear();
         
          filedate = erstdatum.substr(6, 4) + erstdatum.substr(3, 2) + erstdatum.substr(0, 2);
          
          if (Daten.insert(make_pair(filedate, 1)).second == false)
            Daten[filedate]++;
          
          dn = to_string(Daten[filedate]);
          if (Daten[filedate] < 10)  // Einfügung führender Nullen
            dn = "00" + dn;
          else if (Daten[filedate] < 100)
            dn = "0" + dn;
          string filedatestring;
          filedatestring = filedate + "_" + dn;  
         
          // Einrichtung des Verzeichnisses für die aktuelle neue HTML-Datei
          string dateiname_neu = filedatestring + ".html";
          string vollpfad_neu;
          vollpfad_neu = string(neupfad);
          vollpfad_neu.append(filedatestring + "/");
          fs::create_directory(vollpfad_neu);
          string vollpfad_neu2 = vollpfad_neu;
          vollpfad_neu.append(dateiname_neu);
     
         // Öffnen und Schreiben der Ausgabe-HTML-Datei
          datei_neu.open(vollpfad_neu.c_str(), ios_base::out);
          
          if (!datei_neu)
          {
            cerr << "Ziel-HTML-Datei kann nicht geöffnet werden!" << endl;
            return 1;
          }
          else
          {
            datei_neu << "<!DOCTYPE html>\n"
                      << "<html>\n"
                      << "<head>\n"
                      << "  <meta charset=\"UTF-8\">\n"
                      << "  <title>Anzeige " << filedatestring << "</title>\n"
                      << "</head>\n"
                      << "<body>\n"
                      << "  <h2>Anzeige " << filedatestring << "</h2>\n"
                      << "  <p>\n"
                      << "    " << beschreibung << "\n"
                      << "  </p>\n"
                      << "  <table>\n"
                      << "    <tr>\n"
                      << "      <td>Benutzer:</td>\n" 
                      << "      <td>" << benutzer << "</td>\n"
                      << "    </tr>\n"
                      << "    <tr>\n"
                      << "      <td>Datum:</td>\n" 
                      << "      <td>" << erstdatum << "</td>\n"
                      << "    </tr>\n"
                      << "    <tr>\n"                  
                      << "      <td>Ort:</td>\n" 
                      << "      <td>" << ort << "</td>\n"
                      << "    </tr>\n"
                      << "    <tr>\n" 
                      << "      <td>Preis:</td>\n" 
                      << "      <td>" << preis << "</td>\n"
                      << "    </tr>\n"
                      << "  </table>\n" << endl;
                      
                      
          }   
          datei_neu.close();  
          
          
            // Auslesen und Kopieren der Bilddateien aus dem jeweiligen Datenverzeichnis der Original-HTML-Datei
          
          vollpfad_alt.erase(vollpfad_alt.find(".html"));
          vollpfad_alt.append("-Dateien/");
          subdir = opendir(vollpfad_alt.c_str());
          if (subdir == 0)
          {
            vollpfad_alt.erase(vollpfad_alt.find("-Dateien/"));
            vollpfad_alt.append("_files/");
            subdir = opendir(vollpfad_alt.c_str());
          }
          
          i=0; // Zähler für Bilddateien
          
          string vollpfad_neu_bilddateien; // vollständiger Dateiname (mit Pfad) jeder Bilddatei
          
          while(subdir)
          {
            entry = readdir(subdir);
            if (!entry) break;
            name = entry->d_name;
            string sname = string(name);
            if (sname.find("_72") != -1 || sname.find("_59") != -1) // zu kopierende Bilddateien haben das Namensschema _72*JPG, ab September 2019 _59*jpg
            {
              i++; // Bilddateien-Zähler wird um 1 erhöht
              pn = to_string(i);
              vollpfad_neu_bilddateien = vollpfad_neu2 + filedatestring + "_Bild_" + pn + ".jpg";
              
              bilddatei.open(vollpfad_alt + sname, ios_base::in);
              datei_neu.open(vollpfad_neu_bilddateien, ios_base::out);
              
              if (!bilddatei)
              {
                cerr << "Quell-Bilddatei kann nicht geladen werden!" << endl;
                return -1; // Programmabbruch
              }
              else if (!datei_neu)
              {
                cerr << "Ziel-Bilddatei kann nicht geöffnet werden!" << endl;
                return -1; // Programmabbruch
              }
              else 
              {
                while (bilddatei.get(c))
                {
                  datei_neu.put(c);
                }
              }
              
              bilddatei.close();
              datei_neu.close();
            }
          }
         // Einbindung der kopierten Bilddateien in Code der HTML-Ausgabedatei
         datei_neu.open(vollpfad_neu, ios_base::app);
         
         unsigned int j; // Zähler für Schleife
         
         string bilddateiname_rumpf = filedatestring + "_Bild_";
         
         for (j=1; j<=i; j++)
         {
           datei_neu << "  <p>\n"
                     << "    <img src=\"" << bilddateiname_rumpf << j << ".jpg\">\n"
                     << "  </p>\n";
         }
         
           datei_neu          << "</body>\n"
                     << "</html>" << endl;
         
         cout << "Anzeige Nr. " << ad++ << " reduziert!" << endl;            
                     
        }    
        datei_neu.close();
      }  
      return 0;
    }
    

    Bis bald im Khyberspace!

    Yadgar



  • @Yadgar sagte in ifstream::open - Längenbeschränkung für Dateinamen?:

    Linux: Bin nie in solch ein Problem gerannt. ext4 hat ein Dateinamenlimit von 255 Zeichen. Für pfadlängen gibt es für das Dateisystem ext4 gar kein Limit, aber man findet 4k als Limit wenn man googelt was irgendwo definiert ist in einem header. welche Linux/Gnu Funktionen das enforcen ist aber bestimmt schwierig zu sagen.

    Jetzt kann ich im Moment leider nicht sagen, ob ich ext4 oder doch noch ext3 habe... die längsten Dateinamen in meinem Verzeichnis sind so um die 180 Zeichen lang! Woran könnte es denn sonst noch liegen?

    Auch ich habe Probleme mit Datei-, Pfad- oder Kommandozeilen-Längen bisher immer nur unter Windows gehabt. Meist handelte es sich um Build-Skripte von Projekten mit Linux-Fokus die mitunter schonmal extrem tiefe Pfade erzeugen und wahnwitzige, mehrere zig Kilobyte große Kommandozeilen absetzen. Ich glaube nicht, dass das hier das Problem ist, wenn du tatsächlich unter Linux arbeitest.

    Ich würde dir empfehlen, statt einfach nur einen generischen Fehler auszugeben:

    cerr << "Die Datei kann nicht geöffnet werden!" << endl;
    

    ... auch noch zusätzlich verfügbare Fehlerinformationen mitzuliefern:

    #include <cerrno>
    #include <cstring>
    ...
    cerr << "Die Datei kann nicht geöffnet werden: " << std::strerror(errno) << endl;
    

    Beachte dass std::strerror(errno) eine archaische Fehlerbehandlung ist und sich immer auf die zuletzt ausgeführte Funktion bezieht, die potentiell errno setzt. Die Fehlerausgabe sollte also stets direkt nach dem relevanten Funktionsaufruf wie z.B. fstream::open erfolgen, bzw. dazwischen sollten keine Funktionen aufgerufen werden, die ebenfalls errno setzen.

    Edit: Noch ein Hinweis am Rande:

    #include <dirent.h>
    

    dirent.h ist ein POSIX-Header, der z.B. unter Windows üblicherweise nicht zur Verfügung steht. Aus Portabilitätsgründen empfehle ich stattdessen mit std::filesystem::directory_iterator zu arbeiten sofern du keine spezifischen Features benötigst, die keine Entsprechung in der Standardbibliothek haben.



  • @Finnegan sagte in ifstream::open - Längenbeschränkung für Dateinamen?:

    Auch ich habe Probleme mit Datei-, Pfad- oder Kommandozeilen-Längen bisher immer nur unter Windows gehabt. Meist handelte es sich um Build-Skripte von Projekten mit Linux-Fokus die mitunter schonmal extrem tiefe Pfade erzeugen und wahnwitzige, mehrere zig Kilobyte große Kommandozeilen absetzen. Ich glaube nicht, dass das hier das Problem ist, wenn du tatsächlich unter Linux arbeitest.

    Ich bin tatsächlich unter Linux (Debian "Bullseye" 11) unterwegs!

    Ich würde dir empfehlen, statt einfach nur einen generischen Fehler auszugeben:

    cerr << "Die Datei kann nicht geöffnet werden!" << endl;
    

    ... auch noch zusätzlich verfügbare Fehlerinformationen mitzuliefern:

    #include <cerrno>
    #include <cstring>
    ...
    cerr << "Die Datei kann nicht geöffnet werden: " << std::strerror(errno) << endl;
    

    Beachte dass std::strerror(errno) eine archaische Fehlerbehandlung ist und sich immer auf die zuletzt ausgeführte Funktion bezieht, die potentiell errno setzt. Die Fehlerausgabe sollte also stets direkt nach dem relevanten Funktionsaufruf wie z.B. fstream::open erfolgen, bzw. dazwischen sollten keine Funktionen aufgerufen werden, die ebenfalls errno setzen.

    Das Problem ist, dass es erst gar nicht zur Fehlermeldung kommt, die Dateien werden einfach stillschweigend übergangen! Das kann ja eigentlich nur eine Folge des Auswahlkriteriums in Zeile 68 sein - aber ich sehe in den betroffenen Verzeichnissen nirgendwo HTML-Dateien, die anders enden als mit "eBay Kleinanzeigen.html"!

    Bis bald im Khyberspace!

    Yadgar



  • Du solltest unbedingt deinen code besser organisieren.
    z.b. verschiebe den kompletten code, welcher den inhalt einer Datei verarbeitet, in eine separate methode.

    Wie schon Finnegan geschrieben hat.
    Wieso verwendest du opendir, wenn du std::filesystem zur verfügung hast?
    Dann nimm doch lieber std::filesystem::directory_iterator

    https://en.cppreference.com/w/cpp/filesystem/directory_iterator

    Und stell auch mal sicher dass dein code, welcher durch das Verzeichnis iteriert, auch richtig funktioniert und alle Dateien in einem Verzeichnis auch auflistet.

    Des weiteren solltest du dich unbedingt mit der Verwendung eines debuggers vertraut machen



  • @Yadgar sagte in ifstream::open - Längenbeschränkung für Dateinamen?:

    Das Problem ist, dass es erst gar nicht zur Fehlermeldung kommt, die Dateien werden einfach stillschweigend übergangen! Das kann ja eigentlich nur eine Folge des Auswahlkriteriums in Zeile 68 sein - aber ich sehe in den betroffenen Verzeichnissen nirgendwo HTML-Dateien, die anders enden als mit "eBay Kleinanzeigen.html"!

    Dass das "stillschweigend" geschieht, hast du so programmiert. Vielleicht ist sowas hier hilfreich:

    if (vollpfad_alt.find("eBay Kleinanzeigen.html") != -1)
    {
        std::cout<< "processing " << vollpfad_alt << "\n";
        ...
    }
    else
        std::cout<< "skipped " << vollpfad_alt << "\n";
    

    Wenn dann eine Datei unter "skipped" auftaucht die eigentlich verarbeitet werden sollte, kannst du dir überlegen, warum std::string::find bei dieser -1 zurückgibt. Vielleicht nur ein Unterschied in Gross-/Kleinschreibung oder die Datei endet lediglich auf .htm.

    Ansonsten schließe ich mich @firefly an. Ein erster Schritt könnte z.B. sein das, was in diesen riesigen if-Blöcken steht in eigene Funktionen zu packen:

    if (vollpfad_alt.find("eBay Kleinanzeigen.html") != -1)
    {
        std::cout<< "processing " << vollpfad_alt << "\n";
        process_file(vollpfad_alt);
    }
    else
        std::cout<< "skipped " << vollpfad_alt << "\n";
    

Anmelden zum Antworten