Vinzenz Mai: Wie automatisieren? Mit Windows Scripting Host!

Beitrag lesen

Hallo,

Ich habe meinen Code etwas revidiert.

Neu:

  • mit Leerzeichen in Pfadangaben umgehen
  • prinzipiell beliebig viele Folgezeilen mitloggen
  • auch Unterverzeichnisse bearbeiten (true statt false im Aufruf)
  • auch Dateien mit anderen Endungen bearbeiten
/**
 * Schreibt den übergebenen Inhalt in die angegebene Datei. Falls die Datei nicht existiert,
 * wird sie angelegt.
 * <b>write_to_file</b>
 *
 * @param filename {string} Name der Datei, in die geschrieben werden soll
 * @param content  {string} Inhalt, der in die Datei geschrieben werden soll
 * @param mode     {int}    entweder 2 (Inhalt wird überschrieben) oder 8 (Inhalt wird angehängt)
 */
function write_to_file(filename, content, mode) {
    var fso, f;
    fso = new ActiveXObject("Scripting.FileSystemObject");
    f = fso.OpenTextFile(filename, mode);
    f.Write(content);
    f.Close();
}

/**
 * Liest den Inhalt der angegebenen Datei in ein Array aller Zeilen.
 * Die Zeilenenden werden dabei entfernt.
 * Für die Fehlerbehandlung gilt das Gleiche wie bei write_to_file :-(
 * <b>file</b>
 *
 * @param {string} filename Name der Datei, die eingelesen werden soll
 * @return         den Inhalt der Datei als Array der Zeilen
 */
function file(filename) {
    var fso, f, inhalt;
    fso = new ActiveXObject("Scripting.FileSystemObject");
    f = fso.OpenTextFile(filename, 1);  // 1: Nur-Lese-Modus
    inhalt = f.ReadAll();
    f.Close();
    return inhalt.split("\n");
}

/**
 * Extrahiert aus der übergebenen Zeichenkette die "Spalte" mit der Zeilennummer.
 * Die übergebenen Zeichenketten sind vom Format:
 *     D:\test\t3.html:1:Das ist ein Text
 * <b>extract_line_no</b>
 *
 * @param  output_findstr {string} eine Zeile der Ausgabe von FINDSTR /X /N
 * @return                         die Zeilennummer als Integer
 */
function extract_line_no(output_findstr) {
    var content = output_findstr.split(":");
    // Die Zeilennummer steht in der vorletzten Spalte
    // Beachte die explizite Umwandlung in eine Integer
    return parseInt(content[content.length - 2]);
}

/**
 * Extrahiert aus der übergebenen Zeichenkette den Dateinamen.
 * Die übergebenen Zeichenketten sind vom Format:
 *     D:\test\t3.html:1:Das ist ein Text
 *     D:\test\t3.html:3:Das ist ein Text
 * <b>extract_filename</b>
 *
 * @param  output_findstr {string} eine Zeile im Format der Ausgabe von FINDSTR /X /N
 * @return den Dateinamen
 */
function extract_filename(output_findstr) {
    var parts = output_findstr.split(":");
    var filename = parts[0];
    if (parts.length == 4) {
        // Dateiname mit Laufwerksbezeichnung)
        filename = parts[0] + ":" + parts[1];
    }
    return filename;
}

/**
 * Windowsdateinamen verwenden den Backslash als Trennzeichen. Der Backslash ist in
 * JScript jedoch Maskierungszeichen.
 * <b>os_escape</b>
 *
 * @param path {string} Dateiname
 * @return              Zeichenkette mit verdoppelten Backslashes
 */
function os_escape(path) {
 return path.replace("\\", "\\\\");
}

/**
 * Kopiert aus der durch filename spezifizierten Datei die Zeile mit dem Suchstring
 * und die folgenden Zeilen, deren Anzahl durch den Parameter offset bestimmt wird.
 * In der Suchzeile wird das Vorkommen von search durch den Inhalt von replace ersetzt.
 * Der Inhalt der Originalzeile und die offset folgenden wird zurückgegeben.
 * <b>process_file</b>
 *
 * @param filename {string}  Datei, die zu bearbeiten ist
 * @param line_no  {integer} Zeilennummer der gesuchten Zeile
 * @param search   {string}  Suchstring innerhalb der Zeile
 * @param replace  {string}  Ersatz für den Suchstring
 * @param offset   {integer} Anzahl der folgenden Zeilen, die benötigt werden
 * @return                   String mit der Originalzeile und den gewünschten folgenden Zeilen
 */
function process_file(filename, line_no, search, replace, offset) {
   // Lese die zu bearbeitende Datei in ein Array ein
   var content = file(os_escape(filename));

   // Kopiere zu loggende Zeilen ...
   // Beachte: FINDSTR zählt die Zeilen von 1 im Gegensatz zu JScript
   result = content.slice(line_no - 1, line_no + offset);
   // ... und wandle diese in eine Zeichenkette um
   result = result.join("\n");

   // Ersetze in der Suchzeile das Auftreten von search durch den Inhalt von replace
   content[line_no-1] = content[line_no-1].replace(search, replace);

   // Schreibe geänderte Datei weg, überschreibe bisherigen Inhalt
   write_to_file(filename, content.join("\n"), 2);

   return result;
}

/**
 * Filtert aus der Ausgabe von FINDSTR /X /N genau die Zeilen heraus, die das zweite
 * Vorkommen der Suchzeile in der gleichen Datei angeben.
 * <b>filter_list</b>
 *
 * @param output_findstr {string} Ausgabe von FINDSTR /X /N
 * @return                        Array der relevanten Zeilen
 */
function filter_list(output_findstr) {
    // nimmt die Einträge für den Rückgabewert entgegen
 var filtered_list = new Array();
 // für den Dateinamen der zuletzt bearbeiteten Zeile, anfangs leer
 var previous = "";
 // der wievielte Eintrag zur gleichen Datei
 var count = 0;

 // Splitte Ausgabe in Array auf
 var rows = output_findstr.split("\n");
 // Wieviele Zeilen hatte die Ausgabe von FINDSTR
 var rowcount = rows.length;

 // Durchlaufe die Zeilen der Ausgabe
 for ( var i = 0; i < rowcount; i++) {
     // Abschnitte sind durch Doppelpunkt (:) getrennt
  // Vorsicht, Laufwerksbuchstabe kann ein Problem sein
        var parts = rows[i].split(":");
  var current = parts[0];
  if (parts.length == 4) {
   // Angabe mit Laufwerksbuchstabe
   current = parts[0] + ":" + parts[1];
  }
  if (current != previous) {
   // Erstes Auftreten: setze previous auf neuen Wert, initialisiere Zähler
   previous = current;
   count = 1;
  }
  else {
      // Suchzeile mehrfach in der gleichen Datei vorhanden: zähle mit
      count++;
   if (count == 2) {
    // Wir sind genau am zweiten Auftreten interessiert
    filtered_list.push(rows[i]);
   }
   // alle anderen fallen genauso unter den Tisch wie das erste Auftreten :-)
  }
    }
 return filtered_list;
}

/**
 * Setzt in einer Kommandozeile FINDSTR /X /N mit den notwendigen Aufrufparametern ab.
 * Gibt die Ausgabe des Kommandos zurück
 * <b>get_output_findstr</b>
 *
 * @param searchrow {string}  Suchzeile, die exakt übereinstimmen muss
 * @param directory {string}  Verzeichnis, in dem gesucht werden soll
 * @param filemask  {string}  Dateiendung der Dateien, die durchsucht werden sollen
 * @param recursive {boolean} Sollen Unterverzeichnisse mit durchsucht werden
 * @return    Zeichenkette mit der Ausgabe des FINDSTR-Kommandos
 */
function get_output_findstr(searchrow, directory, filemask, recursive) {
    // Wir brauchen ein Shell-Objekt, um das FINDSTR-Kommando abzusetzen
    var WshShell = new ActiveXObject("WScript.Shell");

    // Schalter, die für FINDSTR zu setzen sind:
    // /X               Gibt Zeilen aus, die vollkommen übereinstimmen
    // /N               Gibt die Zeilennummer vor jeder Trefferzeile an
    // /C:Zeichenfolge  Sucht die Zeichenfolge buchstabengetreu
    var options  = "/X /N ";

    // /S: Durchsuche auch alle Unterverzeichnisse
    if (recursive) {
        options += "/S ";
    }

    // Baue das Kommando für den Kommandozeileninterpreter zusammen
    // In der Umgebungsvariablen %COMSPEC% ist der Interpreter zu finden
    // /C sorgt dafür, dass er gleich wieder geschlossen wird
    // Für die Anführungszeichen ist etwas Jonglieren notwendig:
    // Das Kommando ist in doppelte Anführungszeichen zu setzen, der gesuchte Text
    // übrigens auch und genauso die Pfadangabe für die Suche
    var command = "%COMSPEC% /C "
        + "\""                            // Anfang Kommando
        + "FINDSTR "
        + options
        + "/C:"
        + "\"" + searchrow + "\""         // Suchtext in doppelten Anführungszeichen
        + " "                             // Trenne Suchtext und zu durchsuchende Dateien
        + "\"" + os_escape(directory + "\\" + filemask) + "\""
        + "\""                            // Ende Kommando

    // Führe das Kommando aus ...
    var oExec = WshShell.Exec(command);
    // ... und gebe die Ausgabe (Kanal STDOUT, nicht STDERR) zurück
    return oExec.StdOut.ReadAll();
}

/**
 * Überprüft die Eingabeparameter.
 * Aufruf ist wie folgt:
 *     Skriptname searchrow search replace directory logfile
 * Folgende Prüfungen werden vorgenommen:
 *     Anzahl der Parameter muss 5 sein                    32768
 *     searchrow - darf keinen Zeilenumbruch enthalten     32769
 *     search    - muss in searchrow enthalten sein        32770
 *     replace   - darf keinen Zeilenumbruch enthalten     32771
 *     directory - muss ein Verzeichnis sein               32772
 *     logfile   - muss ein gültiger Dateipfad sein        32773
 * Kein Fehler                                             0
 * <b>check_input</b>
 *
 * @param {array} Liste der Aufrufparameter
 * @return        Errorcode
 */
function check_input(args) {
    // Anzahl der Parameter muss 5 sein
    if(args.length != 5) {
        return 32768;
    }
    // im Text der Suchzeile darf kein Zeilenumbruch enthalten sein
    if(args(0).indexOf("\n") != -1){
        return 32769;
    }
    // der zu ersetzende Text muss in der Suchzeile enthalten sein
    if(args(0).indexOf(args(1)) < 0){
        return 32770;
    }
    // der neue Text darf keinen Zeilenumbruch enthalten
    if(args(2).indexOf("\n") != -1){
        return 32771;
    }
    // Prüfungen auf Verzeichnis und Datei benötigen ein FSO
    var fso = new ActiveXObject("Scripting.FileSystemObject");

    // Das angegebene Verzeichnis muss existieren
    if (!fso.FolderExists(os_escape(args(3)))){
        return 32772;
    }

    // Die angegebene Logdatei auch :-)
    if (!fso.FileExists(os_escape(args(4)))){
        return 32773;
    }
    return 0;
}

/*
 * <b>main</b>
 */
function main(args, filemask, offset, recursive) {
    // Überprüfe die Aufrufparameter des Skripts
 var errorcode = check_input(args);
 if (errorcode > 0) {
        // Fehlerhafte Aufrufparameter
     return(errorcode);
 }
 // Suche mit FINDSTR im angegebenen Verzeichnis in allen Dateien mit der
 // angegebenen Dateiendung, ggf. auch in Unterverzeichnissen
 var output    = get_output_findstr(args(0), args(3), filemask, recursive);
 // Filtere die Ausgabe, so dass nur noch die relevanten zweiten Zeilen
 // jeder Datei übrigbleiben
 output        = filter_list(output);

 // Durchlaufe die gefilterte Dateiliste:
 var count     = output.length;
 for ( var i=0; i < count; i++){
     var filename = os_escape(extract_filename(output[i]));
     var line_no  = extract_line_no(output[i]);
     // Extrahiere den zu loggenden Text, Suchzeile plus folgende Zeilen (Anzahl = offset),
     // und schreibe die geänderte Datei weg
     var logged_text = process_file(filename, line_no, args(1), args(2), offset);
     // Hänge den gewünschten Text an die Logdatei an
     write_to_file(os_escape(args(4)), logged_text, 8);
 }
 return 0;
}

/**
 * Nutze das Skript wie vorgesehen:
 * Es werden HTML-Dateien durchsucht,
 * Die Anzahl der ebenfalls zu loggenden Folgezeilen ist 19
 * Unterverzeichnisse werden nicht durchsucht
 */
var args      = WScript.Arguments;
var errorcode = main(args, "*.html", 19, false);
WScript.Echo(errorcode);

Freundliche Grüße Vinzenz