Groovy経由のDOS / Windows用のsplitコマンド

Windows / DOS環境で作業しているときにLinuxで最も見逃しているコマンドの1つは、splitコマンドです。この非常に便利なコマンドを使用すると、大きなファイルを、小さなファイルに必要な行数またはバイト数(またはキロバイトまたはメガバイト)のいずれかの指定によって決定される複数の小さなファイルに分割できます。このような機能には、ファイルを特定のメディアに適合させる、ファイルの長さを制限するアプリケーションでファイルを「読み取り可能」にするなど、多くの用途があります。残念ながら、私はWindowsまたはDOSに相当する分割を認識していません。 PowerShellは、このようなことを行うようにスクリプト化できますが、その実装はPowerShellに固有です。同様の機能を実行するサードパーティ製品も利用できます。しかしながら、これらの既存のソリューションは、Groovyで同等の分割を実装する動機を持っていることを十分に望んでおり、それがこの投稿の主題です。 GroovyはJVMで実行されるため、この実装は理論的には、最新のJava仮想マシン実装を備えた任意のオペレーティングシステムで実行できます。

Groovyベースの分割スクリプトをテストおよびデモンストレーションするには、ある種のソースファイルが必要です。Groovyを使用して、このソースファイルを簡単に生成します。次の単純なGroovyスクリプトbuildFileToSplit.groovyは、分割可能な単純なテキストファイルを作成します。

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

この単純なスクリプトは、Groovyの暗黙的に使用可能な「args」ハンドルを使用して、buildFileToSplit.groovyスクリプトのコマンドライン引数にアクセスします。次に、指定された行数引数に基づいて、サイズの単一ファイルを作成します。各行はほとんどオリジナルではなく、「これは行番号です」と表示され、その後に行番号が続きます。これは派手なソースファイルではありませんが、分割の例では機能します。次の画面のスナップショットは、実行とその出力を示しています。

生成されたsource.txtファイルは次のようになります(ここでは開始と終了のみが示されています)。

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. 

これで、分割できるソースファイルができました。このスクリプトは、より多くのエラー条件をチェックするようにしたため、より多くのコマンドラインパラメーターを処理する必要があるため、そして単にソースファイルを生成したスクリプトよりも多くのことを実行するため、大幅に長くなります。単にsplit.groovyと呼ばれるスクリプトを次に示します。

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

このスクリプトは最適化してモジュール化することができますが、Groovyがプラットフォームに依存しないユーティリティスクリプトを実装するための優れたアプローチをどのように提供するかを示すという目的を果たします。

次の画面のスナップショットは、スクリプトによるGroovyの組み込みCLIサポートの使用法を示しています。

次の2つの画面スナップショットは、ソースファイルを行番号とバイトごとにそれぞれ小さいファイルに分割する方法を示しています(異なるサフィックスとファイル名のオプションを使用しています)。最初の画像は、100行(ソースファイルでは250行)に分割すると3つの出力ファイルが生成されることを示しています。-aオプションは、ファイル名に4つの整数桁を含めることを指定します。Linux分割とは異なり、このスクリプトは、ユーザーが指定した整数の数が必要な出力ファイルの数をカバーするのに十分であることを保証しません。

2番目の画像(次の画像)は、バイト数に基づいてソースファイルを分割し、異なるファイル名と2つの整数のみを番号付けに使用するスクリプトを示しています。

前述のように、このスクリプトは「ラフカット」です。コード自体と機能の点で改善される可能性があります(バイナリ形式をより適切にサポートし、ファイル名のサフィックスが出力ファイルの数に対して十分に長いことを確認するために拡張されました)。ただし、ここでのスクリプトは、Groovyの私のお気に入りの使用法の1つを示しています。それは、使い慣れたJavaおよびGroovyライブラリ(SDKおよびGDK)を使用してプラットフォームに依存しないスクリプトを作成することです。

このストーリー「Groovyを介したDOS / Windowsの分割コマンド」は、もともとJavaWorldによって公開されました。