Commande split pour DOS / Windows via Groovy

L'une des commandes qui me manque le plus de Linux lorsque je travaille dans des environnements Windows / DOS est la commande split. Cette commande extrêmement pratique permet de diviser un gros fichier en plusieurs fichiers plus petits déterminés par la spécification du nombre de lignes ou du nombre d'octets (ou kilo-octets ou mégaoctets) souhaités pour les fichiers plus petits. Il existe de nombreuses utilisations pour de telles fonctionnalités, y compris l'ajustement de fichiers sur certains supports, la création de fichiers «lisibles» par des applications avec des restrictions de longueur de fichier, etc. Malheureusement, je ne connais pas d'équivalent fractionné pour Windows ou DOS. PowerShell peut être scripté pour faire quelque chose comme ceci, mais cette implémentation est spécifique à PowerShell. Il existe également des produits tiers disponibles qui exécutent des fonctionnalités similaires. cependant,ces solutions existantes laissent juste assez à désirer pour que j'aie la motivation d'implémenter un split équivalent dans Groovy et c'est le sujet de ce post. Étant donné que Groovy fonctionne sur la JVM, cette implémentation pourrait théoriquement être exécutée sur n'importe quel système d'exploitation avec une implémentation de machine virtuelle Java moderne.

Pour tester et démontrer le script fractionné basé sur Groovy, un type de fichier source est requis. J'utiliserai Groovy pour générer facilement ce fichier source. Le script Groovy simple suivant, buildFileToSplit.groovy, crée un fichier texte simple qui peut être divisé.

#!/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"}) 

Ce script simple utilise le descripteur "args" implicitement disponible de Groovy pour accéder aux arguments de ligne de commande du script buildFileToSplit.groovy. Il crée ensuite un fichier unique de taille basée sur l'argument du nombre de lignes fourni. Chaque ligne est en grande partie non originale et indique "Ceci est la ligne #" suivi du numéro de ligne. Ce n'est pas un fichier source sophistiqué, mais cela fonctionne pour l'exemple de fractionnement. La capture d'écran suivante montre son exécution et sa sortie.

Le fichier source.txt généré ressemble à ceci (seuls le début et la fin de celui-ci sont affichés ici):

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. 

Un fichier source est maintenant disponible pour être divisé. Ce script est beaucoup plus long car je l'ai fait vérifier plus de conditions d'erreur, car il doit gérer plus de paramètres de ligne de commande, et simplement parce qu'il fait plus que le script qui a généré le fichier source. Le script, simplement appelé split.groovy, est illustré ci-après:

#!/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 } 

Ce script pourrait être optimisé et mieux modularisé, mais il remplit son objectif de démontrer comment Groovy fournit une approche intéressante pour implémenter des scripts utilitaires indépendants de la plate-forme.

La capture d'écran suivante montre l'utilisation par le script de la prise en charge de l'interface CLI intégrée de Groovy.

Les deux instantanés d'écran suivants illustrent la division du fichier source en fichiers plus petits par numéros de ligne et octets respectivement (et en utilisant différentes options de suffixe et de nom de fichier). La première image montre que trois fichiers de sortie sont générés lorsqu'ils sont divisés en 100 lignes (250 lignes dans le fichier source). L'option -a spécifie que quatre entiers seront dans le nom de fichier. Contrairement à la division Linux, ce script ne garantit pas que le nombre d'entiers fourni par l'utilisateur soit suffisant pour couvrir le nombre de fichiers de sortie nécessaires.

La deuxième image (image suivante) montre le script divisant le fichier source en fonction du nombre d'octets et en utilisant un nom de fichier différent et seulement deux entiers pour la numérotation.

Comme mentionné ci-dessus, ce script est une "première coupe". Il pourrait être amélioré en termes de code lui-même ainsi qu'en termes de fonctionnalité (étendu pour mieux supporter les formats binaires et pour s'assurer que les suffixes de nom de fichier sont suffisamment longs pour le nombre de fichiers de sortie). Cependant, le script décrit ici l'une de mes utilisations préférées de Groovy: écrire des scripts indépendants de la plate-forme à l'aide de bibliothèques Java et Groovy familières (SDK et GDK).

Cette histoire, "Split Command for DOS / Windows Via Groovy" a été publiée à l'origine par JavaWorld.