split Befehl für DOS / Windows über Groovy

Einer der Befehle, die ich unter Linux am meisten vermisse, wenn ich in Windows / DOS-Umgebungen arbeite, ist der Befehl split. Mit diesem äußerst praktischen Befehl kann eine große Datei in mehrere kleinere Dateien aufgeteilt werden, die durch die Angabe der Anzahl der Zeilen oder der Anzahl der für die kleineren Dateien gewünschten Bytes (oder Kilobyte oder Megabyte) bestimmt werden. Es gibt viele Verwendungsmöglichkeiten für solche Funktionen, einschließlich des Anpassen von Dateien an bestimmte Medien, des "Lesens" von Dateien durch Anwendungen mit Dateilängenbeschränkungen usw. Leider ist mir kein Split-Äquivalent für Windows oder DOS bekannt. PowerShell kann für so etwas per Skript ausgeführt werden, diese Implementierung ist jedoch spezifisch für PowerShell. Es sind auch Produkte von Drittanbietern verfügbar, die ähnliche Funktionen ausführen. Jedoch,Diese vorhandenen Lösungen lassen gerade genug zu wünschen übrig, dass ich die Motivation habe, ein Split-Äquivalent in Groovy zu implementieren, und das ist das Thema dieses Beitrags. Da Groovy auf der JVM ausgeführt wird, kann diese Implementierung theoretisch auf jedem Betriebssystem mit einer modernen Java Virtual Machine-Implementierung ausgeführt werden.

Zum Testen und Demonstrieren des Groovy-basierten Split-Skripts ist eine Art Quelldatei erforderlich. Ich werde Groovy verwenden, um diese Quelldatei einfach zu generieren. Das folgende einfache Groovy-Skript, buildFileToSplit.groovy, erstellt eine einfache Textdatei, die aufgeteilt werden kann.

#!/usr/bin/env groovy // // buildFileToSplit.groovy // // Accepts single argument for number of lines to be written to generated file. // If no number of lines is specified, uses default of 100,000 lines. // if (!args) { println "\n\nUsage: buildFileToSplit.groovy fileName lineCount\n" println "where fileName is name of file to be generated and lineCount is the" println "number of lines to be placed in the generated file." System.exit(-1) } fileName = args[0] numberOfLines = args.length > 1 ? args[1] as Integer : 100000 file = new File(fileName) // erases output file if it already existed file.delete() 1.upto(numberOfLines, {file << "This is line #${it}.\n"}) 

Dieses einfache Skript verwendet das implizit verfügbare "args" -Handle von Groovy, um auf Befehlszeilenargumente für das Skript buildFileToSplit.groovy zuzugreifen. Anschließend wird eine einzelne Datei mit der Größe basierend auf dem angegebenen Argument für die Anzahl der Zeilen erstellt. Jede Zeile ist weitgehend unoriginal und lautet "Dies ist Zeile #", gefolgt von der Zeilennummer. Es ist keine ausgefallene Quelldatei, aber es funktioniert für das Aufteilungsbeispiel. Der nächste Screenshot zeigt den Lauf und die Ausgabe.

Die generierte Datei source.txt sieht folgendermaßen aus (hier werden nur Anfang und Ende angezeigt):

This is line #1. This is line #2. This is line #3. This is line #4. This is line #5. This is line #6. This is line #7. This is line #8. This is line #9. This is line #10. . . . This is line #239. This is line #240. This is line #241. This is line #242. This is line #243. This is line #244. This is line #245. This is line #246. This is line #247. This is line #248. This is line #249. This is line #250. 

Es steht jetzt eine Quelldatei zum Teilen zur Verfügung. Dieses Skript ist erheblich länger, weil ich es auf mehr Fehlerbedingungen prüfen ließ, weil es mehr Befehlszeilenparameter verarbeiten muss und einfach weil es mehr tut als das Skript, das die Quelldatei generiert hat. Das Skript, einfach split.groovy genannt, wird als nächstes gezeigt:

#!/usr/bin/env groovy // // split.groovy // // Split single file into multiple files similarly to how Unix/Linux split // command works. This version of the script is intended for text files only. // // This script does differ from the Linux/Unix variant in certain ways. For // example, this script's output messages differ in several cases and this // script requires that the name of the file being split is provided as a // command-line argument rather than providing the option to provide it as // standard input. This script also provides a "-v" ("--version") option not // advertised for the Linux/Unix version. // // CAUTION: This script is intended only as an illustration of using Groovy to // emulate the Unix/Linux script command. It is not intended for production // use as-is. This script is designed to make back-up copies of files generated // from the splitting of a single source file, but only one back-up version is // created and is overridden by any further requests. // // //marxsoftware.blogspot.com/ // import java.text.NumberFormat NEW_LINE = System.getProperty("line.separator") // // Use Groovy's CliBuilder for command-line argument processing // def cli = new CliBuilder(usage: 'split [OPTION] [INPUT [PREFIX]]') cli.with { h(longOpt: 'help', 'Usage Information') a(longOpt: 'suffix-length', type: Number, 'Use suffixes of length N (default is 2)', args: 1) b(longOpt: 'bytes', type: Number, 'Size of each output file in bytes', args: 1) l(longOpt: 'lines', type: Number, 'Number of lines per output file', args: 1) t(longOpt: 'verbose', 'Print diagnostic to standard error just before each output file is opened', args: 0) v(longOpt: 'version', 'Output version and exit', args: 0) } def opt = cli.parse(args) if (!opt || opt.h) {cli.usage(); return} if (opt.v) {println "Version 0.1 (July 2010)"; return} if (!opt.b && !opt.l) { println "Specify length of split files with either number of bytes or number of lines" cli.usage() return } if (opt.a && !opt.a.isNumber()) {println "Suffix length must be a number"; cli.usage(); return} if (opt.b && !opt.b.isNumber()) {println "Files size in bytes must be a number"; cli.usage(); return} if (opt.l && !opt.l.isNumber()) {println "Lines number must be a number"; cli.usage(); return} // // Determine whether split files will be sized by number of lines or number of bytes // private enum LINES_OR_BYTES_ENUM { BYTES, LINES } bytesOrLines = LINES_OR_BYTES_ENUM.LINES def suffixLength = opt.a ? opt.a.toBigInteger() : 2 if (suffixLength  1 ? opt.arguments()[1] : "x" try { file = new File(filename) if (!file.exists()) { println "Source file ${filename} is not a valid source file." System.exit(-4) } int fileCounter = 1 firstFileName = "${prefix}${fileSuffixFormat.format(0)}" if (verboseMode) { System.err.println "Creating file ${firstFileName}..." } outFile = createFile(firstFileName) if (bytesOrLines == LINES_OR_BYTES_ENUM.BYTES) { int byteCounter = 0 file.eachByte { if (byteCounter < numberBytes) { outFile << new String(it) } else { nextOutputFileName = "${prefix}${fileSuffixFormat.format(fileCounter)}" if (verboseMode) { System.err.println "Creating file ${nextOutputFileName}..." } outFile = createFile(nextOutputFileName) outFile << new String(it) fileCounter++ byteCounter = 0 } byteCounter++ } } else { int lineCounter = 0 file.eachLine { if (lineCounter < numberLines) { outFile << it << NEW_LINE } else { nextOutputFileName = "${prefix}${fileSuffixFormat.format(fileCounter)}" if (verboseMode) { System.err.println "Creating file ${nextOutputFileName}..." } outFile = createFile(nextOutputFileName) outFile << it << NEW_LINE fileCounter++ lineCounter = 0 } lineCounter++ } } } catch (FileNotFoundException fnfEx) { println System.properties println "${fileName} is not a valid source file: ${fnfEx.toString()}" System.exit(-3) } catch (NullPointerException npe) { println "NullPointerException encountered: ${npe.toString()}" System.exit(-4) } /** * Create a file with the provided file name. * * @param fileName Name of file to be created. * @return File created with the provided name; null if provided name is null or * empty. */ def File createFile(String fileName) { if (!fileName) { println "Cannot create a file from a null or empty filename." return null } outFile = new File(fileName) if (outFile.exists()) { outFile.renameTo(new File(fileName + ".bak")) outFile = new File(fileName) } return outFile } 

Dieses Skript könnte optimiert und besser modularisiert werden, erfüllt jedoch den Zweck, zu demonstrieren, wie Groovy einen guten Ansatz für die Implementierung plattformunabhängiger Dienstprogramm-Skripte bietet.

Der nächste Screenshot zeigt, wie das Skript die integrierte CLI-Unterstützung von Groovy verwendet.

Die nächsten beiden Screenshots zeigen, wie Sie die Quelldatei nach Zeilennummern und Bytes in kleinere Dateien aufteilen (und unterschiedliche Optionen für Suffixe und Dateinamen verwenden). Das erste Bild zeigt, dass drei Ausgabedateien generiert werden, wenn sie in 100 Zeilen aufgeteilt werden (250 Zeilen in der Quelldatei). Die Option -a gibt an, dass sich vier ganzzahlige Stellen im Dateinamen befinden. Im Gegensatz zum Linux-Split garantiert dieses Skript nicht, dass die vom Benutzer angegebene Anzahl von Ganzzahlen ausreicht, um die Anzahl der erforderlichen Ausgabedateien abzudecken.

Das zweite Bild (nächstes Bild) zeigt das Skript, das die Quelldatei basierend auf der Anzahl der Bytes aufteilt und einen anderen Dateinamen und nur zwei Ganzzahlen für die Nummerierung verwendet.

Wie oben erwähnt, ist dieses Skript ein "Rohschnitt". Es könnte sowohl hinsichtlich des Codes selbst als auch hinsichtlich der Funktionalität verbessert werden (erweitert, um Binärformate besser zu unterstützen und sicherzustellen, dass die Dateinamensuffixe für die Anzahl der Ausgabedateien ausreichend lang sind). Das Skript hier zeigt jedoch eine meiner Lieblingsanwendungen von Groovy: das Schreiben plattformunabhängiger Skripte mit bekannten Java- und Groovy-Bibliotheken (SDK und GDK).

Diese Geschichte "Split Command für DOS / Windows über Groovy" wurde ursprünglich von JavaWorld veröffentlicht.