基本读/写, 逐行读取文件, 高级文件读取操作, 高级文件写入操作, 列出目录内容, 创建目录, 创建链接, 复制文件, 移动文件, 重命名文件, 删除文件, 查看文件属性, 获取和修改文件权限, HTTP/FTP文件, 计算记录数
要访问和处理文件,请使用file方法,该方法返回给定文件路径字符串的文件系统对象:
myFile = file('/some/path/to/my_file.file')
file 方法可以引用文件或目录,具体取决于字符串路径在文件系统中引用的内容。
当使用通配符*,?,[]和{}时,参数被解释为一个全局路径匹配器,file方法返回一个列表对象,其中包含文件名与指定模式匹配的文件的路径,如果没有找到匹配,则返回一个空列表:
listOfFiles = file('/some/path/*.fa')
默认情况下,通配符不匹配目录或隐藏文件。例如,如果你想在结果列表中包含隐藏文件,可以添加可选参数hidden:
listWithHidden = file('some/path/*.fa', hidden: true)
这里是文件的可用选项:
名称 | 描述 |
---|---|
glob | 当为true时,将字符*,?,[]和{}作为通配符,否则将它们作为普通字符处理(默认为true) |
type | 返回的路径类型,file, dir或any(默认:file) |
hidden | 当为true时,结果路径中包含隐藏文件(默认:false) |
maxDepth | 要访问的目录级别的最大数目(默认:没有限制) |
followLinks | 当在目录树遍历期间符号链接为true时,否则将它们视为文件(默认为true) |
checkIfExists | 当为true时抛出文件系统中不存在指定路径的异常(默认值:false) |
Tip
如果你是一个Java极客,你会有兴趣知道文件方法返回一个Path对象,它允许你使用在Java程序中常见的方法。
参见: Channel.fromPath
基本读/写
给定一个file变量,如前一个例子中所示,使用file方法声明,读取文件就像获取文件的text属性的值一样简单,它以字符串值的形式返回文件内容:
print myFile.text
类似地,你可以通过简单地将一个字符串值赋给文件的text属性来保存它:
myFile.text = 'Hello world!'
Note
现有的文件内容被赋值操作覆盖,它也隐式地创建不存在的文件。
为了在不删除现有内容的情况下将字符串值追加到文件中,可以使用append方法:
myFile.append('Add this line\n')
或者使用左移位操作符,这是向文件追加文本内容的一种更习惯的方法:
myFile << 'Add a line more\n'
二进制数据也可以以同样的方式管理,只是使用文件属性bytes而不是文本。因此,下面的示例读取文件并以字节数组的形式返回其内容:
binaryContent = myFile.bytes
或者你可以保存一个字节数组数据缓冲区到一个文件,通过简单地写:
myFile.bytes = binaryBuffer
Warning
上述方法在单个变量或缓冲区中一次读取和写入所有文件内容。因此,在处理大文件时不建议使用它们,因为大文件需要更有效的内存方法,例如逐行读取文件或使用固定大小的缓冲区。
逐行读取文件
为了逐行读取文本文件,你可以使用file对象提供的readLines()方法,它以字符串列表的形式返回文件内容:
myFile = file('/some/my_file.txt')
allLines = myFile.readLines()
for( line : allLines ) {
println line
}
这也可以写成更习惯的语法:
file('/some/my_file.txt')
.readLines()
.each { println it }
Note
readLines()方法立即读取所有文件内容,并返回包含所有行的列表。因此,不要使用它来读取大文件。
要处理一个大文件,使用方法eachLine,它每次只向内存中读入一行:
count = 0
myFile.eachLine { str ->
println "line ${count++}: $str"
}
高级文件读取操作
Reader类和InputStream类分别为读取文本和二进制文件提供了很好的控制。
newReader方法为给定的文件创建一个Reader对象,允许你以单个字符、行或字符数组的形式读取内容:
myReader = myFile.newReader()
String line
while( line = myReader.readLine() ) {
println line
}
myReader.close()
withReader方法的工作原理相似,但当您完成文件处理后,它会自动为您调用close方法。因此,前面的例子可以更简单地写成:
myFile.withReader {
String line
while( line = it.readLine() ) {
println line
}
}
方法newInputStream和withInputStream的工作方式类似。主要的区别是它们创建了一个用于写入二进制数据的InputStream对象。
下面是最重要的读取文件的方法:
名称 | 描述 |
---|---|
getText | 以字符串值的形式返回文件内容 |
getBytes | 以字节数组的形式返回文件内容 |
readLines | 逐行读取文件,并以字符串列表的形式返回内容 |
eachLine | 逐行迭代文件,应用指定的闭包 |
eachByte | 逐字节遍历文件,应用指定的闭包 |
withReader | 打开要读取的文件,并允许您使用Reader对象访问它 |
withInputStream | 打开要读取的文件,并允许您使用InputStream对象访问它 |
newReader | 返回读取文本文件的Reader对象 |
newInputStream | 返回一个用于读取二进制文件的InputStream对象 |
阅读Reader和InputStream类的Java文档,了解更多关于从文件中读取数据的方法。
高级文件写入操作
Writer和OutputStream类分别为文本和二进制文件的编写提供了良好的控制,包括对单个字符或字节的低级操作,以及对大文件的支持。
例如,给定两个文件对象sourceFile和targetFile,下面的代码将第一个文件的内容复制到第二个文件中,用X替换所有的U字符:
sourceFile.withReader { source ->
targetFile.withWriter { target ->
String line
while( line=source.readLine() ) {
target << line.replaceAll('U','X')
}
}
}
下面是最重要的写入文件的方法:
名称 | 描述 |
---|---|
setText | 将字符串值写入文件 |
setBytes | 将字节数组写入文件 |
write | 将字符串写入文件,替换任何现有内容 |
append | 将字符串值追加到文件中而不替换现有内容 |
newWriter | 创建Writer对象,允许您将文本数据保存到文件中 |
newPrintWriter | 创建一个PrintWriter对象,该对象允许您将格式化文本写入文件 |
newOutputStream | 创建允许将二进制数据写入文件的OutputStream对象 |
withWriter | 将指定的闭包应用于Writer对象,完成后关闭它 |
withPrintWriter | 将指定的闭包应用于PrintWriter对象,并在完成时关闭它 |
withOutputStream | 将指定的闭包应用于OutputStream对象,并在完成时关闭它 |
请阅读Writer, PrintWriter和OutputStream类的Java文档,了解更多关于将数据写入文件的方法。
列出目录内容
让我们假设您需要遍历您所选择的目录。你可以定义指向它的myDir变量:
myDir = file('/any/path')
获取目录列表最简单的方法是使用list或listFiles方法,它们返回目录的一级元素(文件和目录)的集合:
allFiles = myDir.list()
for( def file : allFiles ) {
println file
}
Note
list和listFiles之间的唯一区别是前者返回一个字符串列表,而后者返回一个允许您访问文件元数据的文件对象列表,例如大小、最后修改时间等。
eachFile方法只允许迭代第一级元素(就像listFiles)。和其他each- methods一样,each files接受一个闭包作为参数:
myDir.eachFile { item ->
if( item.isFile() ) {
println "${item.getName()} - size: ${item.size()}"
}
else if( item.isDirectory() ) {
println "${item.getName()} - DIR"
}
}
上述方法有几种变体可用。完整列表见下表。
名称 | 描述 |
---|---|
eachFile | 迭代第一级元素(文件和目录)。Read more |
eachDir | 只迭代第一级目录。 Read more |
eachFileMatch | 遍历名称与给定过滤器匹配的文件和目录。Read more |
eachDirMatch | 遍历名称与给定过滤器匹配的目录。Read more |
eachFileRecurse | 按深度优先遍历目录元素。Read more |
eachDirRecurse | 遍历目录深度优先(忽略常规文件)。Read more |
参见:Channel fromPath方法。
创建目录
给定一个表示不存在目录的文件变量,如下所示:
myDir = file('/any/path/unknown_dir')
mkdir 方法在给定的路径上创建一个目录,如果目录创建成功则返回 true,否则返回 false:
result = myDir.mkdir()
println result ? "OK" : "Cannot create directory: $myDir"
这里?:为三元运算符,result为真,则返回?后:前的内容,否则返回:后的内容
Note
如果父目录不存在,上述方法将失败并返回false(即这里不能递归创建)。
mkdirs方法创建以file对象命名的目录,包括任何不存在的父目录:
myDir.mkdirs()
创建链接
对于一个文件,mklink方法使用指定的路径作为参数为该文件创建一个文件系统链接:
myFile = file('/some/path/file.txt')
myFile.mklink('/user/name/link-to-file.txt')
可选参数表:
名称 | 描述 |
---|---|
hard | true表示创建硬链接,否则表示创建软链接(也就是符号链接)。(默认值:false) |
overwrite | 当true覆盖任何同名的现有文件时,否则抛出FileAlreadyExistsException(默认:false) |
Note Linux中软硬链接区别
- 硬链接不会创建inode,即使用的inode都是一样的。软链接会创建新的inode。
- 硬链接的访问属性和源文件一模一样,没有l的标识。软链接的访问属性写明了是l,且访问权限不能设置,只能是777,真正的权限取决于源文件。
- 如果移动源文件,则软链接找不到,而硬链接则没有这个问题,因为软链接存的是文件的位置。
- 硬链接是一种引用关系,一个源文件建立1个硬链接,引用计数加1,删除一个文件(硬链接文件或者源文件),引用计数减1,当引用计数为0时,真正删除文件。删除源文件软链接只是找不到了目标文件。
- 不能创建目录的硬链接,不能在不同的文件系统的文件间建立硬链接,软链接则没有这些限制。
复制文件
copyTo方法将文件复制到新文件或目录中,或将目录复制到新目录中:
myFile.copyTo('new_name.txt')
Note
如果目标文件已经存在,它将被新的文件替换。还要注意,如果目标是一个目录,源文件将被复制到该目录中,并保持文件的原始名称。
当源文件是一个目录时,它的所有内容都被复制到目标目录:
myDir = file('/some/path')
myDir.copyTo('/some/new/path')
If the target path does not exist, it will be created automatically.
Tip
copyTo方法模仿Linux命令cp -r
移动文件
你可以使用moveTo方法移动文件:
myFile = file('/some/path/file.txt')
myFile.moveTo('/another/path/new_file.txt')
Note
当与目标同名的文件已经存在时,它将被源文件替换。还要注意,当目标是一个目录时,文件将被移动到(或移动到)该目录中,并保持文件的原始名称。
当源目录是一个目录时,所有目录中内容都被移动到目标目录:
myDir = file('/any/dir_a')
myDir.moveTo('/any/dir_b')
请注意,上述示例的结果取决于目标目录是否存在。如果目标目录存在,源文件会被移动到目标目录,结果是:
/any/dir_b/dir_a
如果目标目录不存在,则源目录将被重命名为目标名称,导致路径:
/any/dir_b
Tip
moveTo方法模仿Linux命令mv
重命名文件
您可以通过简单地使用renameTo方法重命名文件或目录:
myFile = file('my_file.txt')
myFile.renameTo('new_file_name.txt')
删除文件
file方法delete删除给定路径下的文件或目录,如果操作成功返回true,否则返回false:
myFile = file('/some/file.txt')
result = myFile.delete()
println result ? "OK" : "Can delete: $myFile"
Note
此方法只删除不包含任何文件或子目录的目录。要删除一个目录及其所有内容(即删除它可能包含的所有文件和子目录),使用deleteDir方法。
查看文件属性
使用file方法创建的文件变量可以使用以下方法:
名称 | 描述 |
---|---|
getName | 获取文件名,例如/some/path/file.txt -> file.txt |
getBaseName | 获取没有扩展名的文件名,例如/some/path/file.tar.gz -> file.tar |
getSimpleName | 获取没有任何扩展名的文件名,例如/some/path/file.tar.gz -> file |
getExtension | 获取文件扩展名,例如/some/path/file.txt -> txt |
getParent | 获取文件的父路径,例如/some/path/file.txt -> /some/path |
size | 获取以字节为单位的文件大小 |
exists | 如果文件存在则返回true,否则返回false |
isEmpty | 如果文件长度为零或不存在则返回true,否则返回false |
isFile | 如果它是一个普通文件,例如不是目录,则返回true |
isDirectory | 如果文件是目录则返回true |
isHidden | 如果文件被隐藏则返回true |
lastModified | 返回文件最后修改的时间戳,也就是Linux epoch时间 |
例如,下面的行打印文件名和大小:
println "File ${myFile.getName() size: ${myFile.size()}"
Tip
任何以get前缀开始的方法名称的调用都可以省略get前缀和ending()圆括号。因此写入myFile. getname()和myFile.name是完全相同的,而myFile. getbasename()和myFile是完全相同的。baseName等等。
获取和修改文件权限
给定一个代表文件(或目录)的文件变量,getPermissions方法返回一个9个字符的字符串,使用Linux符号符号表示文件的权限,例如rw-rw-r--:
permissions = myFile.getPermissions()
类似地,setPermissions方法使用相同的符号设置文件的权限:
myFile.setPermissions('rwxr-xr-x')
setPermissions方法的第二个版本用三个数字设置文件的权限,分别表示所有者、组和其他权限:
myFile.setPermissions(7,5,5)
更多请了解 File permissions numeric notation
HTTP/FTP文件
Nextflow提供了HTTP/S和FTP协议的透明集成,将远程资源作为本地文件系统对象处理。简单地指定资源URL作为文件对象的参数:
pdb = file('http://files.rcsb.org/header/5FID.pdb')
然后,你可以像前面描述的那样访问它作为一个本地文件:
println pdb.text
上面的一行代码打印远程PDB文件的内容。前面的部分提供了一些代码示例,展示了如何流式传输或复制文件的内容。
Note
HTTP/S和FTP文件不支持写入和列表操作。
计算记录数
countLines
countLines方法计算文本文件中的行数。
def sample = file('/data/sample.txt')
println sample.countLines()
Note
下面fasta、fastq文件如果是gz压缩格式的,将被自动解压
countFasta
countFasta方法计算FASTA格式文件中的记录数。
def sample = file('/data/sample.fasta')
println sample.countFasta()
countFastq
countFastq方法计算FASTQ格式文件中的记录数。
def sample = file('/data/sample.fastq')
println sample.countFastq()