04-Nextflow 文件和I/O

基本读/写, 逐行读取文件, 高级文件读取操作, 高级文件写入操作, 列出目录内容, 创建目录, 创建链接, 复制文件, 移动文件, 重命名文件, 删除文件, 查看文件属性, 获取和修改文件权限, 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, PrintWriterOutputStream类的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中软硬链接区别

  1. 硬链接不会创建inode,即使用的inode都是一样的。软链接会创建新的inode。
  2. 硬链接的访问属性和源文件一模一样,没有l的标识。软链接的访问属性写明了是l,且访问权限不能设置,只能是777,真正的权限取决于源文件。
  3. 如果移动源文件,则软链接找不到,而硬链接则没有这个问题,因为软链接存的是文件的位置。
  4. 硬链接是一种引用关系,一个源文件建立1个硬链接,引用计数加1,删除一个文件(硬链接文件或者源文件),引用计数减1,当引用计数为0时,真正删除文件。删除源文件软链接只是找不到了目标文件。
  5. 不能创建目录的硬链接,不能在不同的文件系统的文件间建立硬链接,软链接则没有这些限制。

复制文件

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 <source> <target>的语义,但有以下注意事项:Linux工具通常将以斜杠结尾的路径(例如/some/path/name/)视为目录,而将非斜杠结尾的路径(例如/some/path/name)视为普通文件,Nextflow(由于它使用了Java文件API)将这两个路径视为相同的文件系统对象。如果路径存在,它将根据它的实际类型(即作为一个常规文件或目录)进行处理。如果该路径不存在,则将其视为常规文件,并自动创建任何缺失的父目录。


移动文件

你可以使用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 <source> <target>的语义,与上面对copyTo给出的警告相同。


重命名文件

您可以通过简单地使用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()

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容