原文連結 譯文連結 譯者:jackwang
groovy提供了豐富的方法來操作io流。當然你也可以使用标準的java代碼來進行這些操作。但是groovy提供了更多友善的方式來操作檔案,流…
你可以先看看下面列舉的一些方法:
the io.file class : http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/file.html
the io.inputstream class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/inputstream.html
the io.outputstream class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/outputstream.html
the io.reader class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/reader.html
the io.writer class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/io/writer.html
the nio.file.path class: http://docs.groovy-lang.org/latest/html/groovy-jdk/java/nio/file/path.html
下面的一些小節将提供一些示例來示範如何使用這些類,如果你想檢視所有方法的詳細用法,請閱讀gdk的接口文檔。
作為開篇的第一個示例,我們來看看如何使用groovy來讀檔案并且列印讀到的所有行:
<code>new file(basedir, 'haiku.txt').eachline { line -> println line }</code>
groovy的eachline方法是file類自動加載并且可有有許多的變體,比如如果你想知道行号,你可以使用以下的變體:
<code>new file(basedir, 'haiku.txt').eachline { line, nb -> println "line $nb: $line" }</code>
在eachline方法體裡,抛出任何異常後該方法都可以確定正常将檔案流關閉。groovy的其他操作檔案流的方法也提供了該特性。
比如說,有些場景你可能更喜歡用reader,該類的檔案操作方法依然可以自動管理檔案流。在下面的這個例子裡,即便操作檔案過程中抛出了異常,檔案流依然可以正常關閉:
<code>def count = 0, maxsize = 3 new file(basedir,"haiku.txt").withreader { reader -> while (reader.readline()) { if (++count > maxsize) { throw new runtimeexception('haiku should only have 3 verses') } } }</code>
如果你想将一個文本檔案中的所有行放到一個list裡,你可以這樣寫:
<code>def list = new file(basedir, 'haiku.txt').collect {it}</code>
你甚至還可以使用as方法将一個檔案内容放到一個數組中:
<code>def array = new file(basedir, 'haiku.txt') as string[]</code>
很多時候你想将一個檔案的内容放到一個byte數組,你覺得需要多少代碼來實作呢?groovy将這個操作變成了一行代碼:
<code>byte[] contents = file.bytes</code>
使用io并不僅僅是操作檔案,事實上,更多的時候你需要操作輸入/輸出流,這就是為什麼groovy提供了非常豐富的方法來實作這一需求,你可以參見這個文檔:inputstream
舉個例子,你可以非常容易地從一個檔案中擷取一個輸入流:
<code>def is = new file(basedir,'haiku.txt').newinputstream() // do something ... is.close()</code>
但是這個方式需要你手動關閉輸入流,事實上groovy還提供了一種更加通用和快捷的方式,那就是使用withinputstream來操作:
<code>new file(basedir,'haiku.txt').withinputstream { stream -> // do something ... }</code>
有時候你并不是想讀檔案而是寫檔案。這時一種方式是使用writer:
<code>new file(basedir,'haiku.txt').withwriter('utf-8') { writer -> writer.writeline 'into the ancient pond' writer.writeline 'a frog jumps' writer.writeline 'water’s sound!' }</code>
事實上對于上面這個簡單的例子,使用 <<操作符就綽綽有餘了:
<code>new file(basedir,'haiku.txt') << '''into the ancient pond a frog jumps water’s sound!'''</code>
當然,我們并不是僅僅處理文本内容,但你也可以使用writer或直接寫位元組:
<code>file.bytes = [66,22,11]</code>
當然你也可以直接處理輸出流,比如說下面的例子示範了如何建立一個輸出流并寫入到一個檔案:
<code>def os = new file(basedir,'data.bin').newoutputstream() // do something ... os.close()</code>
但是這個例子要求你手動關閉輸出流。一種更好的做法是使用withoutputstream,任何時候隻要抛出了異常它都會關閉流:
<code>new file(basedir,'data.bin').withoutputstream { stream -> // do something ... }</code>
在腳本上下文裡,一種很常見的場景是周遊檔案樹來找到特定的檔案進行特定的處理。groovy提供了多種方法來做這個事情。比如說可以操作某個目錄下的全部檔案:
<code>dir.eachfile { file -> println file.name } //(1) dir.eachfilematch(~/.*\.txt/) { file -> println file.name } //(2)</code>
列舉給定目錄下的每個檔案
在給定目錄下查找比對格式的檔案
你經常需要處理更深層次的目錄,可以使用 eachfilerecurse:
<code>dir.eachfilerecurse { file -> println file.name } //(1)</code>
dir.eachfilerecurse(filetype.files) { file ->
println file.name
} //(2)
遞歸列舉所有檔案和目錄
僅僅遞歸列舉檔案
需要更加複雜的周遊技術,可以使用 traverse方法,需要你設定一個特定的遞歸辨別來終止遞歸:
<code>dir.traverse { file -> if (file.directory && file.name=='bin') { filevisitresult.terminate //(1) } else { println file.name filevisitresult.continue //(2) } }</code>
如果目前檔案是一個目錄并且名字是bin,停止周遊
列印檔案名并繼續
在java裡,使用java.io.dataoutputstream 和 java.io.datainputstream類來序列化和反序列化資料是非常常見的。groovy裡,這步操作将變得更加容易,比如,你可以序列化資料到一個檔案然後使用下面的代碼反序列:
<code>boolean b = true string message = 'hello from groovy' // serialize data into a file file.withdataoutputstream { out -> out.writeboolean(b) out.writeutf(message) } // ... // then read it back file.withdatainputstream { input -> assert input.readboolean() == b assert input.readutf() == message }</code>
類似地,如果你想要序列化的資料實作了serializable接口,你可以使用一個對象輸出流來處理,如下面的示例所示:
<code>person p = new person(name:'bob', age:76) // serialize data into a file file.withobjectoutputstream { out -> out.writeobject(p) } // ... // then read it back file.withobjectinputstream { input -> def p2 = input.readobject() assert p2.name == p.name assert p2.age == p.age }</code>
前面的章節描述了使用groovy來處理檔案,readers或流是一件很簡單的是。但是在一些領域向系統管理者或開發經常需要和外部程序進行互動。
groovy提供了一種簡單的方式來執行指令行程序。僅僅需要将指令行寫成字元串然後調用execute方法。舉個例子,子啊一個*nix機器上(或者一台安裝了*nix指令執行環境的windows機器上)你可以執行下面的代碼:
<code>def process = "ls -l".execute() (1) println "found text ${process.text}" (2)</code>
在外部程序執行ls指令
處理輸出并且傳回文本
execute方法傳回一個java.lang.process執行個體,可以使用in/out/err流來處理,通過傳回值可以檢視處理情況。
eg:這裡有一個和上面指令類似但是現在是每次處理一個結果流:
<code>def process = "ls -l".execute() (1) process.in.eachline { line -> (2) println line (3) }</code>
對每個輸入流進行處理
列印line的内容
in 相當于标準輸出指令中的輸入流, out指代你發送到程序(标準輸入流)中的資料的流。
記住,對于内置的shell指令需要有特殊的處理,是以如果你想在一台windows機器上列一個某個目錄的所有檔案可以這樣寫:
<code>def process = "dir".execute() println "${process.text}"</code>
當出現 cannot run program “dir”: createprocess error=2, the system cannot find the file specified. 時你将收到一個ioexception
這是因為dir指令是windows shell(cmd.exe)内置的指令。不能僅僅是執行dir,你應該這樣寫:
<code>def process = "cmd /c dir".execute() println "${process.text}"</code>
同樣,因為這個功能使用了java.lang.process 類,這個類的一些缺陷就必須考慮進去,javadoc關于這個類是這樣說的:
因為一些原生平台僅僅提供受限緩沖區大小的标準輸入輸出流,是以對于失敗的寫輸入流操作或讀輸出流操作可能會造成程序阻塞甚至死鎖。
因為這一點,groovy 提供了一個額外的幫助方法類使得流處理起來更加友善。
下面的例子是如何無阻塞處理素有的輸出(包括錯誤流輸出):
<code>def p = "rm -f foo.tmp".execute([], tmpdir) p.consumeprocessoutput() p.waitfor()</code>
consumeprocessoutput 也有一些變體來使用stringbuffer,inputstream,outputstream等等,完整的例子可以參考gdk api for java.lang.process
除此之外,也有一個pipeto指令(對應 | (管道))使得輸出流可以承接到另外一個程序的輸入流。
這有一些示例的使用:
<code>proc1 = 'ls'.execute() proc2 = 'tr -d o'.execute() proc3 = 'tr -d e'.execute() proc4 = 'tr -d i'.execute() proc1 | proc2 | proc3 | proc4 proc4.waitfor() if (proc4.exitvalue()) { println proc4.err.text } else { println proc4.text }</code>
處理錯誤:
<code>def sout = new stringbuilder() def serr = new stringbuilder() proc2 = 'tr -d o'.execute() proc3 = 'tr -d e'.execute() proc4 = 'tr -d i'.execute() proc4.consumeprocessoutput(sout, serr) proc2 | proc3 | proc4 [proc2, proc3].each { it.consumeprocesserrorstream(serr) } proc2.withwriter { writer -> writer << 'testfile.groovy' } proc4.waitfororkill(1000) println "standard output: $sout" println "standard error: $serr"</code>