Romero: DIR-Funktion in VBS oder FileExists in Javascript mit Platzhalter

Hallöchen ihr lieben,

ich steh grad vor einem Problem. Ich würde gern eine Vielzahl an Ordnern mit einer noch größeren Anzahl an Dateien auslesen. Dazu habe ich bereits (in einem anderen Projekt) in VBA mittels einer DIR-Funktion mir diese Dateien auflisten lassen.

Nun brauche ich die selbe Funktionalität in Javascript bzw. im einzubindenden VBS.

Speziell geht es dadrum, dass ich nicht genau weiß, wie meine Eingangs-Datei weiter geht.
Beispiel: Meine Daten, welche ich als Suchstring angebe, lautet so: A123_99999_000D Meine Datei in den jeweiligen Ordnern, lauten so: A123_99999_000D_10A oder A123_99999_000D_20A oder A123_99999_000D_10B (-> neuer Ordner) etc.

Mittels fso.FileExists(DateiName) in Javascript, brauch ich die exakte Benennung, um diese Datei zu finden und ggf. auszulesen. Da ich aber nicht weiß, wie diese Datei weiter geht, brauch ich an dieser Stelle einen Art Platzhalter/Wildcards. Das zu javascript.

Nun VBS.
Wenn ich VBS nutze, und es dann ins Javascript einbinden möchte, wollte ich die DIR-Funktion nehmen. Aber auch da bringt er mir, dass mein Script, DIR nicht kennt, obwohl es in VBA nutzbar ist. Ich weiß, dass VBA und VBS nicht das Gleiche ist, aber zum größten Teil identisch. Bei DIR kann ich den Platzhalter mittels * angeben und er listet mir da alle dazugehörigen Komponenten auf. Da hier die Frage, welche ähnliche oder gleiche Funktion gibt es, wie dieses DIR?

Ich habe zwar die Dateien mir suchen lassen, mittels VBS und einem CreateObject("Scripting.FileSystemObject") und einem GetFolder(Path).Files, welche ich mit "Each A in GetFolder(Path).Files" auslesen kann, aber bei mehr als 200k Daten, hängt er mehr als 10min dran, was nicht Sinn und Zweck ist.

So, nun die Frage(n) an Euch.

Kann ich bei Javascript - fso.FileExists mit Platzhalter arbeiten? Oder den dazugehörigen Pfad so gestalten, dass Platzhalter erlaubt sind? Oder Wie ann ich bei VBScript - DIR oder eine ähnliche Funktion so nutzen, wie es bei VBA auch der Fall ist?

LG Romero

  1. Mittels fso.FileExists(DateiName) in Javascript, brauch ich die exakte Benennung, um diese Datei zu finden und ggf. auszulesen.

    fso.FileExists ist mir unbekannt. Woher stammt diese Funktion?

    Da ich aber nicht weiß, wie diese Datei weiter geht, brauch ich an dieser Stelle einen Art Platzhalter/Wildcards.

    node-glob kann das.

    1. fso.FileExists ist mir unbekannt. Woher stammt diese Funktion?

      Na hier zum Beispiel FileExists()

      1. fso.FileExists ist mir unbekannt. Woher stammt diese Funktion?

        Na hier zum Beispiel FileExists()

        Dann bin ich fälschlicherweise davon ausgegangen, dass du in einer node.js-Umgebung bist. node-globe wird dir also nicht helfen können. Aus der Quelle kann ich leider nicht herauslesen, welches Laufzeit-System dort behandelt wird.

  2. Das Problem ist, dass der eigentliche Directory Scan schnell geht (GetFolder(xyz).Files), aber das Abfragen des Dateinamens dazu führt, dass er ein File-Objekt für diesen Eintrag initialisiert. Und das DAUERT. Warum es bei Dir 10 Minuten dauert, ist mir nicht ganz klar; ich habe mir einen Ordner mit 150K Dateien angelegt und da dauerte es 15s, alle Dateinamen nach einem Muster zu durchsuchen. Liegt dein Ordner im Netzwerk?

    Wenn Du das Scripting.FileSystemObject nutzen kannst, dann auch bestimmt das WScript.Shell Objekt, gelle?

    Du kannst dich an diesem Snippet hier orientieren. Das ermittelt die gewünschten Dateinamen über einen DIR Befehl per Kommandozeile. Dieser Ablauf ist schnell. An Stelle von a* musst Du das gewünschte Suchmuster eintragen. Sollten Deine Dateinamen Leerstellen enthalten, musst Du auch noch Anführungszeichen drumherum setzen. Windows Kommandozeile eben.

    Das Script führt mittels cmd.exe /c dir den DIR Befehl der Windows Befehlszeile aus. Die DIR Option /b führt dazu, dass DIR nur die Dateinamen ausgibt und nicht den übrigen Schnickschnack drumherum. Exec gibt Dir ein WshScriptExec Objekt zurück, das die Standard-Streams des gestarteten Prozesses umleitet und dem Aufrufer zur Verfügung stellt. DIR schreibt auf die Standardausgabe, die kannst Du deshalb lesen und bekommst damit alle Dateinamen geliefert die auf das Muster passen.

    set WshShell = WScript.CreateObject("WScript.Shell")
    counter = 0
    Set wx = WshShell.Exec("cmd.exe /c dir a* /b")
    Do
        line = wx.StdOut.ReadLine()
        counter = counter + 1
    Loop While Not wx.Stdout.atEndOfStream
    
    WScript.Echo "Gezählt: " + cstr(counter)
    

    Rolf

    1. Hy Rolf

      Liegt dein Ordner im Netzwerk?

      Ja liegt es, leider :(

      Wenn Du das Scripting.FileSystemObject nutzen kannst, dann auch bestimmt das WScript.Shell Objekt, gelle?

      Jepp, kann ich nutzen :)

      set WshShell = WScript.CreateObject("WScript.Shell")
      counter = 0
      Set wx = WshShell.Exec("cmd.exe /c dir a* /b")
      Do
          line = wx.StdOut.ReadLine()
          counter = counter + 1
      Loop While Not wx.Stdout.atEndOfStream
      
      WScript.Echo "Gezählt: " + cstr(counter)
      

      Ich habe es mal an einem direkten Ordner + Dateibeispiel gemacht, und es kam die richtige Anzahl heraus, welche in diesem Ordner liegen.

      Aber dazu habe ich noch ein paar Fragen: Wie kann ich dieses a* mit 2 getrennten Variablen ersetzen? Da ich in Javascript eine Schleife starte, über alle Ordner, und dann ich diese "Hauptnamen" in den jeweiligen Ordnern suchen lasse.

      Javascript:

      var Ordner_arr = new Array("C:\Test\A", "C:\Test\B", "C:\Test\C", "C:\Test\D");
      var Datei = "A123-10000-020AB";
      
      for( var i = 0; i < Ordner_arr.length; i++ )
      {
      	var test = Auslesen_Dateien( Ordner_arr[i], Datei.replace(/-/g, "_") )
      };
      

      VBS:

      Function Auslesen_Dateien( Hauptpfad, Datei )
      	Dim WshShell, WScript
      	Dim wx, line
      			
      	set WshShell = CreateObject("WScript.Shell")
      	counter = 0
      	Set wx = WshShell.Exec("cmd.exe /c dir Hauptpfad & '\' & Datei* /b")
      	Do
      		line = wx.StdOut.ReadLine()
      		counter = counter + 1
      	Loop While Not wx.Stdout.atEndOfStream
      			
      	MsgBox "Gezählt: " + cstr(counter)
      End Function
      

      Das sind die beiden Funktionen. Gebe ich den Pfad direkt händisch an, so gibt er mir die korrekte Anzahl raus. Nur wie kann ich das mit Variablen machen? Wenn ich es so wie oben mache, bringt er mir zwar beim ersten Durchlauf die richtige Gesamtanzahl aus, aber müsste er da nicht für jeden Ordner die korrekte Anzahl anzeigen? Also auch mal eine "0", wenn da nix gefunden wurde?

      Und wie kann ich mir den Namen anzeigen lassen? Mit MsgBox wx (Anhand oben), zeigt er mir ein Fehler an. Mit MsgBox wx.Name ebenso. Ich würde mir die Namen hier in ein Array speichern lassen, wo ich dann deren Inhalt auslesen kann.

      Danke für deine Hilfe bisher...

      LG Romero

      1. Habe jetzt wenig Zeit und sitze "nur" mit dem Handy da.

        Du musst den Parameter für exec mit Stringverkettung zusammenbauen. Die Variablennamen in den String hineinzuschreiben kann nicht klappen.

        MsgBox ist VBA, in VBS wirst du, wenn es überhaupt geht, eine Methode in einem der verfügbaren Objekte nehmen müssen. Muss ich auch nachgucken, das kannst du schneller selbst.

        Gib bescheid, wie du zurecht kommst, eventuell kann ich am späteren Abend nochmal gucken.

        Rolf

        1. Hy Rolf

          Habe jetzt wenig Zeit und sitze "nur" mit dem Handy da.

          Kein Problem. Dennoch Danke für deine bisherige Hilfe :)

          Du musst den Parameter für exec mit Stringverkettung zusammenbauen. Die Variablennamen in den String hineinzuschreiben kann nicht klappen.

          Ja, ich habe nun den Parameter wie folgt deklariert, und es funktioniert.

          WshShell.Exec("cmd.exe /c dir " & Replace(Hauptpfad, "/", "\") & "\" & Replace(Datei, "/", "\") & "* /b")
          

          MsgBox ist VBA, in VBS wirst du, wenn es überhaupt geht, eine Methode in einem der verfügbaren Objekte nehmen müssen. Muss ich auch nachgucken, das kannst du schneller selbst.

          Doch, mit MsgBox bekomm ich meine "Meldungen" angezeigt. Ich kann mir mit...

          MsgBox Hauptpfad & " =======> " & & wx.StdOut.ReadAll()
          

          ...all meine Dateien in dem jeweiligen Ordner anzeigen lassen, die passend zu der Variable 'Datei' sind. Aber wie gekomm ich diese angezeigten Dateien in eine Variable gespeichert? Ich habe es simple mit

          Dim abc
          Do
          	line = wx.StdOut.ReadLine()
          	abc = wx.StdOut.ReadAll()
          	MsgBox "= 1 => " & wx.StdErr.AtEndOfStream & " ===> " & wx.StdOut.ReadAll() & " (" & counter & ")"
          Loop While Not wx.StdOut.AtEndOfStream
          

          für wx.StdOut.ReadAll() zeigt er mir alle Dateien außer die 1. Position an. Füge ich diese MsgBox aber außerhalb der Do-Schleife ein, zeigt er mir ALLE gefundenn Dateien an. Dennoch kann ich diese nicht in eine Variable speichern um damit im späteren Verlauf zu arbeiten, so wie es z.B. unter diesem Link Variable setzen beschrieben ist (:/ laut Google).

          Und noch eine Frage dazu. Wie kann ich die Ausführung des Exec() im Hintergrund laufen lassen?

          Danke für deine Hilfe. Und bisher komm ich damit gut zurecht. Auch mit viel Experimentier-Freudigkeit meinerseits :)

          LG Romero

          1. So, jetzt sitze ich auf dem trimmdich Rad und habe Zeit. Bloß immer noch das Handy :-/

            Stdout und Stderr sind Streams, d.h. was du da herausgelesen hast, ist weg. Deswegen hast du in line die erste Zeile und in abc den Rest. Readall liest bis zum Ende des Streams - ich muss allerdings zugeben, dass ich nicht weiß wie sich das Objekt in wx bei Pipe-Streams verhält. Der DIR Befehl läuft echt parallel zur Leseschleife, und wenn ein readAll ihn bis zum AKTUELLEN Ende liest, kann es sein, dass der noch laufende DIR Befehl danach noch ein paar Zeilen nachlegt. Deswegen readLine in der Schleife, der liest immer eine ganze Zeile. Wenn du die Dateinamen weiter verarbeiten willst, speicherst du sie in der Schleife am besten in einem Array (Tabelle). Für eine Anzeige in einer MsgBox kannst du die Tabelleneinträge zu einem langen String zusammensetzen, mit jeweils einem CHR(13) (Neue Zeile) dazwischen.

            Ich hoffe, das ist dir jetzt nicht zu abstrakt...

            Exec im Hintergrund, tja, Exec führt das Programm als separaten Prozess aus. Über stdout liest du sozusagen live mit, was das laufende CMD schreibt. Hintergründiger geht kaum. Wenn du das ganze VBScript im Hintergrund laufen lassen willst, ist das was anderes, aber dann musst du vermutlich einiges umbauen. Einfach mal einen Arbeitsthread starten ist im cscript meines Wissens nicht vorgesehen. Um da was empfehlen zu können, müsste ich mehr Kontext haben (und nicht so schwitzen wie gerade...)

            Rolf

            1. Dann bin ich froh, dass ich nicht alleine dich so sehr ins schwitzen bringe :/ :) :)

              Also ich hab da aufgrund deines Threads, ausprobiert.

              Set WshShell = CreateObject("WScript.Shell")
              Set wx = WshShell.Exec("cmd.exe /c dir " & Replace(Hauptpfad_EINZELMATTE, "/", "\") & "\" & Replace(Haupt_EINZELMATTE, "/", "\") & "* /b")
              
              If wx.StdErr.AtEndOfStream = true Then
              	Do
              		line = wx.StdOut.ReadLine()
              		abc = wx.StdOut.ReadAll()
              	Loop While Not wx.StdOut.AtEndOfStream
              				
              	MsgBox line
              	MsgBox abc
              End If
              

              die Variablen 'line' und 'abc' bringen mir nun das richtige aus. Wie ich aber das ReadAll() in ein Array schreibe, bin ich grad noch drüber her (durchs experimentieren). Aber bisher hast du mir schon einmal sehr weiter geholfen. Wie ich die Variablen ins Javascript übergebe, bleibt noch ne kleine Hürde, aber das habe ich bereits in einem anderen Projekt bewerkstelligen können.

              Bis hierher einen großen Dank.

              LG Romero

              PS: weiter gutes Schwitzen :)

              1. Eine Frage dazu hab ich noch. Wie kriege ich beim .ReadAll() die Dateinamen in ein Array? Beim .ReadLine() ist es nur "eine Zeile" die ich in eine Var speichere und weiter verwende. Aber beim .ReadAll() gibt er ja alles auf ein Mal aus? Muss ich da noch ne Schleife drumbasteln? Oder mit (0-n)-Indexen arbeiten?

                Die Variablen kann ich dann mittels (bei großen VBS-mehrdimensionalen-Verschachtelungen) new VBArray(VAR) auslesen.

                LG Romero

                1. Hallo Romero,

                  lass die Finger von readAll. Dann hast Du alle Dateinamen in einem String stehen und müsstest darin nach Zeilenumbrüchen suchen, um die einzelnen Namen zu finden. Mit readLine ist es einfacher, dann hast Du die Namen gleich einzeln und kannst sie einsammeln. Das ist in VBscript allerdings etwas mühsam, weil es keine Array mit automatischer Längendynamik hat. Man muss es von Hand bauen.

                  In VBScript musst Du dich entweder mit REDIM herumschlagen oder einfach ein Maximum an Treffern voraussetzen. Ich mach's mal mit REDIM. Der Code ist so wie er hier steht ungetestet (mein Testcode sieht etwas anders aus), müsste aber klappen.

                  Dim WshShell, wx, pattern, line
                  Dim anzZeilen, maxZeilen
                  
                  set WshShell = CreateObject("WScript.Shell")
                  pattern = Replace(Hauptpfad, "/", "\") & "\" & Replace(Datei, "/", "\") & "*"
                  set wx = WshShell.Exec("cmd.exe /c dir " & pattern & " /b")
                  anzNamen = 0
                  redim namen(10)
                  do
                     line = wx.StdOut.ReadLine()
                     if anzNamen > UBound(namen) then
                        redim preserve namen(UBound(namen)*2)
                     end if
                     namen(anzNamen) = line
                     anzNamen = anzNamen + 1
                  loop while Not wx.StdOut.AtEndOfStream
                  redim preserve namen(anzNamen)
                  

                  Mit REDIM namen(10) erzeugst Du ein dynamisches Array, dessen Größe Du modifizieren kannst. Eine naive Lösung würde die Größe pro Schleifendurchlauf um 1 erhöhen, das ist aber ein furchtbares Geschüttele im Speicher. Darum macht man es so, wie oben gezeigt: Größe immer weiter verdoppeln, bis alles drin ist, und dann den Überschuss entfernen.

                  Nach dieser Fummelei hast Du in namen ein Array mit exakt so vielen Einträgen, wie Du Treffer hattest, und kannst es weiterverarbeiten wie du willst.

                  Aber mal blöd gefragt: Wieso tust Du Dir diesen krummen Hund VBScript überhaupt an? Grundsätzlich gibt es das WScript-Objekt und seine Methoden auch in JScript. Hier ein Beispiel für eine JScript-Implementierung (herauszufinden, wie ein VBScript Replace in JScript aussieht, überlasse ich Dir):

                  wsh = WScript.CreateObject("WScript.Shell");
                  wshExec = wsh.Exec("CMD /C DIR  /b");
                  
                  namen = [];
                  
                  while(!wshExec.StdOut.AtEndOfStream)
                  {
                     namen.push(wshExec.StdOut.ReadLine());
                  }
                  
                  WScript.Echo ("Gefunden: " + namen.length + " Namen");
                  for (i=0; i<namen.length; i++)
                     WScript.Echo ("   " + i + ": " + namen[i]);
                  

                  Rolf