Nextflow脚本语言是Groovy编程语言的扩展。Groovy是Java虚拟机的强大编程语言。Nextflow语法已专门用于以声明性方式简化计算流水线的编写。

Nextflow可以执行任何Groovy代码或对JVM平台使用任何库。

有关Groovy编程语言的详细说明,请参考以下链接:

1 语言基础

1.1 Hello World

打印某些内容与使用print或println方法之一一样容易。

println "Hello, World!"

两者之间的唯一区别是该println方法隐式地将新行字符附加到打印的字符串。

1.2 变数

要定义变量,只需为其分配一个值:

x = 1
println x

x = new java.util.Date()
println x

x = -3.1499392
println x

x = false
println x

x = "Hi"
println x

1.3 List

可以通过将列表项放在方括号中来定义List对象:

myList = [1776, -1, 33, 99, 0, 928734928763]

您可以使用方括号符号(索引从0开始)访问列表中的给定项目:

println myList[0]

为了获得列表的长度,请使用以下size方法:

println myList.size()

了解有关列表的更多信息:

1.4 Maps

Map用于存储关联数组或字典。它们是异构的命名数据的无序集合:

scores = [ "Brett":100, "Pete":"Did not finish", "Andrew":86.87934 ]

请注意,映射中存储的每个值可以具有不同的类型。Brett是整数,Pete字符串和Andrew浮点数。

我们可以通过两种主要方式访问地图中的值:

println scores["Pete"]
println scores.Pete

要将数据添加到地图或修改地图,其语法类似于将值添加到列表:

scores["Pete"] = 3
scores["Cedric"] = 120

了解有关Map的更多信息:

1.5 多重初始化

数组或列表对象可用于一次分配给多个变量:

(a, b, c) = [10, 20, 'foo']
assert a == 10 && b == 20 && c == 'foo'

赋值运算符左侧的三个变量由列表中的相应项目初始化。

在Groovy文档中阅读有关多重分配的更多信息。

1.6 if-else语句

任何编程语言最重要的功能之一就是能够在不同条件下执行不同代码。最简单的方法是使用if构造:

x = Math.random()
if( x < 0.5 ) {
    println "You lost."
}
else {
    println "You won!"
}

1.7 Strings

可以通过将文本括在单引号或双引号('"字符)中来定义字符串:

println "he said 'cheese' once"
println 'he said "cheese!" again'

字符串可以与串联+

a = "world"
print "hello " + a + "\n"

1.8 字符串插值

单引号和双引号字符串之间有一个重要区别:双引号字符串支持变量插值,而单引号字符串则不支持。

实际上,双引号字符串可以通过在变量名称前加上$字符作为前缀来包含任意变量的值,也可以使用${expression}语法类似于Bash / shell脚本来包含任何表达式的值:

foxtype = 'quick'
foxcolor = ['b', 'r', 'o', 'w', 'n']
println "The $foxtype ${foxcolor.join()} fox"

x = 'Hello'
println '$x + $y'

此代码打印:

The quick brown fox
$x + $y

1.9 多行字符串

可以通过用三重单引号或双引号定界来定义跨多行的文本块:

text = """ hello there James how are you today? """

像在Bash / shell脚本中一样,在多行字符串中用\字符终止行可防止换行符将该行与随后的行分开:

myLongCmdline = """ blastp \ -in $input_query \ -out $output_file \ -db $blast_database \ -html """

result = myLongCmdline.execute().text

在前面的例子中,blastp和它的-in-out-db-html开关和它们的参数是有效的单条线。

1.10 闭包

简单地说,闭包是可以作为参数传递给函数的代码块。因此,您可以定义一个代码块,然后像字符串或整数一样传递它。

更正式地说,您可以定义一个类对象。

square = { it * it }

表达式周围的花括号it * it告诉脚本解释器将此表达式视为代码。标识符是一个隐式变量,它表示调用函数时传递给函数的值。

编译后,函数对象被赋值给变量。square正如前面显示的任何其他变量赋值一样。现在我们可以这样做:

println square(9)

得到值81。

在我们发现我们可以传递这个函数之前,square作为其他函数或方法的论据。一些内置函数将这样的函数作为参数。一个例子是collect方法:

[ 1, 2, 3, 4 ].collect(square)

这个表达式表示:创建一个值为1、2、3和4的数组,然后调用它的collect方法,传入我们前面定义的闭包。这个collect方法在数组中的每个项中运行,调用项的闭包,然后将结果放入一个新数组中,结果如下:

[ 1, 4, 9, 16 ]

有关可以使用闭包作为参数调用的更多方法,请参见Groovy GDK文档.

默认情况下,闭包接受一个名为it,但您也可以使用多个自定义参数创建闭包。例如,方法Map.each()可以使用两个参数进行闭包,并将钥匙以及相关的价值中的每个键值对Map。在这里,我们使用明显的变量名key和value在我们结束时:

printMapClosure = { key, value ->
    println "$key = $value"
}

[ "Yue" : "Wu", "Mark" : "Williams", "Sudha" : "Kumari" ].each(printMapClosure)

输出:

Yue=Wu
Mark=Williams
Sudha=Kumari

闭包还有另外两个重要的特性。首先,它可以访问定义它的作用域中的变量。

其次,可以在匿名方式,意思是它没有被命名,并且定义在需要使用它的地方。

作为展示这两个特性的示例,请参见以下代码片段:

myMap = ["China": 1 , "India" : 2, "USA" : 3]

result = 0
myMap.keySet().each( { result+= myMap[it] } )

println result

更多关于闭包请参看:Groovy文档

1.11 正则表达式

正则表达式为程序员提供了从字符串中匹配和提取模式的能力。

正则表达式可通过~/pattern/语法和=~==~操作员。

使用=~若要检查给定模式是否发生在字符串中的任何位置,请执行以下操作:

assert 'foo' =~ /foo/       // return TRUE
assert 'foobar' =~ /foo/    // return TRUE

使用==~若要检查字符串是否与给定的正则表达式模式完全匹配,请执行以下操作。

assert 'foo' ==~ /foo/       // return TRUE
assert 'foobar' ==~ /foo/    // return FALSE

值得注意的是,~操作符创建JavaPattern对象,而=~操作符创建JavaMatcher对象。

x = ~/abc/
println x.class
// prints java.util.regex.Pattern

y = 'some string' =~ /abc/
println y.class
// prints java.util.regex.Matcher

正则表达式支持是从Java导入的。Java的正则表达式语言和API在模式Java文档

1.12 字符串替换

若要替换给定字符串中出现的模式,请使用replaceFirstreplaceAll方法:

x = "colour".replaceFirst(/ou/, "o")
println x
// prints: color

y = "cheesecheese".replaceAll(/cheese/, "nice")
println y
// prints: nicenice

1.13 捕获组

您可以匹配包含组的模式。首先,使用=~接线员。然后,可以索引Matcher对象以查找匹配:matcher[0]返回一个列表,表示字符串中正则表达式的第一个匹配项。第一个List元素是匹配整个正则表达式的字符串,其余元素是匹配每个组的字符串。

下面是它的工作原理:

programVersion = '2.7.3-beta'
m = programVersion =~ /(\d+)\.(\d+)\.(\d+)-?(.+)/

assert m[0] ==  ['2.7.3-beta', '2', '7', '3', 'beta']
assert m[0][1] == '2'
assert m[0][2] == '7'
assert m[0][3] == '3'
assert m[0][4] == 'beta'

应用一些语法,只需用一行代码就可以完成相同的操作:

programVersion = '2.7.3-beta'
(full, major, minor, patch, flavor) = (programVersion =~ /(\d+)\.(\d+)\.(\d+)-?(.+)/)[0]

println full    // 2.7.3-beta
println major   // 2
println minor   // 7
println patch   // 3
println flavor  // beta

1.14 删除字符串的一部分

可以删除String使用正则表达式模式计算。找到的第一个匹配被替换为空字符串:

// define the regexp pattern
wordStartsWithGr = ~/(?i)\s+Gr\w+/

// apply and verify the result
('Hello Groovy world!' - wordStartsWithGr) == 'Hello world!'
('Hi Grails users' - wordStartsWithGr) == 'Hi users'

从字符串中删除前5个字符的单词:

assert ('Remove first match of 5 letter word' - ~/\b\w{5}\b/) == 'Remove match of 5 letter word'

从字符串中删除带有尾随空格的第一个数字:

assert ('Line contains 20 characters' - ~/\d+\s+/) == 'Line contains characters'

2 文件和I/O

若要访问和处理文件,请使用file方法,该方法返回给定文件路径字符串的文件系统对象:

myFile = file('some/path/to/my_file.file')

这个file方法可以引用档案或目录,取决于文件系统中字符串路径引用的内容。

当使用通配符时*?[]{},则该参数被解释为格罗布路径匹配器和file方法返回一个List对象,该对象保存名称与指定模式匹配的文件的路径,如果没有找到匹配,则返回空列表:

listOfFiles = file('some/path/*.fa')

默认情况下,通配符字符与目录或隐藏文件不匹配。例如,如果要在结果列表中包括隐藏文件,请添加可选参数hidden:

listWithHidden = file('some/path/*.fa', hidden: true)

以下是file可供选择的方案:

名字 描述
glob 当值为true时,解释字符*, ?, []{}作为GLOB通配符,否则将它们作为普通字符处理(默认值:true)
type 返回路径的类型file, dirany(默认值:file)
hidden 当值为true时,在结果路径中包括隐藏文件(默认值:false)
maxDepth 要访问的目录级别的最大数量(默认值:not limit)
followLinks 当值为true时,在目录树遍历过程中遵循符号链接,否则将它们视为文件(默认值:true)
checkIfExists 当值为true时,引发指定路径的异常在文件系统中不存在(默认值:false)

2.1 基本读写

给定一个文件变量,该变量使用file方法,如前面的示例所示,读取文件就像获取文件的值一样容易。text属性,该属性将文件内容作为字符串值返回:

print myFile.text

类似地,只需将字符串值分配给文件,就可以将字符串值保存到文件中。text属性:

myFile.text = 'Hello world!'

为了在不删除现有内容的情况下将字符串值追加到文件中,可以使用append方法:

myFile.append('Add this line\n')

或使用<<运算符,一种将文本内容附加到文件中的更惯用的方法:

myFile << 'Add a line more\n'

二进制数据可以使用相同的方式管理,只需使用file属性bytes而不是text。因此,下面的示例读取文件并将其内容作为字节数组返回:

binaryContent = myFile.bytes

也可以将字节数组数据缓冲区保存到文件中,只需编写:

myFile.bytes = binaryBuffer

警告:上述方法同时在一个变量或缓冲区中读取和写入所有文件内容。因此,在处理大文件时不建议这样做,因为大文件需要更有效的内存方法,例如逐行读取文件或使用固定大小的缓冲区。

2.2 逐行读取文件

为了逐行读取文本文件,可以使用以下方法readLines()由file对象提供,它将文件内容作为字符串列表返回:

myFile = file('some/my_file.txt')
allLines  = myFile.readLines()
for( line : allLines ) {
    println line
}

若要处理大文件,请使用以下方法eachLine,它一次只读取一行到内存中:

count = 0
myFile.eachLine {  str ->
        println "line ${count++}: $str"
    }

2.3 高级文件读取操作

Reader和分别InputStream提供用于分别读取文本和二进制文件的精细控制。

方法newReader创建读者对象,该文件允许将内容读入单个字符、行或字符数组:

myReader = myFile.newReader()
String line
while( line = myReader.readLine() ) {
    println line
}
myReader.close()

方法withReader工作类似,但自动调用close方法,当您完成文件处理后。因此,前面的示例可以编写得更简单一些,如下所示:

myFile.withReader {
    String line
    while( line = myReader.readLine() ) {
        println line
    }
}

方法newInputStreamwithInputStream同样的工作。主要区别在于它们创建了一个InputStream对象,用于编写二进制数据。

2.4 高级文件写入操作

这个WriterOutputStream类分别为编写文本和二进制文件提供了良好的控制,包括对单个字符或字节的低级操作,以及对大文件的支持。

例如,给定两个文件对象sourceFile和targetFile,下面的代码将第一个文件的内容复制到第二个文件中,替换所有U字符为X:

sourceFile.withReader { source ->
    targetFile.withWriter { target ->
        String line
        while( line=source.readLine() ) {
            target << line.replaceAll('U','X')
        }
    }
}

2.5 列表目录内容

让我们假设您需要遍历您选择的目录。您可以定义myDir指向它的变量:

myDir = file('any/path')

获取目录列表的最简单方法是使用以下方法listlistFiles,返回目录的第一级元素(文件和目录)的集合:

allFiles = myDir.list()
for( def file : allFiles ) {
    println file
}

2.6 创建目录

给定一个表示不存在目录的文件变量,如下所示:

myDir = file('any/path')

方法mkdir在给定路径上创建一个目录,如果该目录已成功创建返回true,否则false

result = myDir.mkdir()
println result ? "OK" : "Cannot create directory: $myDir"

2.7 创建链接

给定文件后,该方法使用指定为参数的路径为该文件mklink创建文件系统链接:

myFile = file('/some/path/file.txt')
myFile.mklink('/user/name/link-to-file.txt')

2.8 复制文件

方法copyTo将文件复制到新文件或目录,或将目录复制到新目录:

myFile.copyTo('new_name.txt')

当源文件是目录时,其所有内容都复制到目标目录:

myDir = file('/some/path')
myDir.copyTo('/some/new/path')


If the target path does not exist, it will be created automatically.

2.9 移动文件

可以使用以下方法移动文件moveTo:

myFile = file('/some/path/file.txt')
myFile.moveTo('/another/path/new_file.txt')

当源是目录时,所有目录内容都移动到目标目录:

myDir = file('/any/dir_a')
myDir.moveTo('/any/dir_b')

2.10 重命名文件

您可以简单地使用renameTo档案方法:

myFile = file('my_file.txt')
myFile.renameTo('new_file_name.txt')

2.11 删除文件

文件方法delete删除给定路径上的文件或目录,如果操作成功返回true,否则返回false:

myFile = file('some/file.txt')
result = myFile.delete()
println result ? "OK" : "Can delete: $myFile"

2.12 获取和修改文件权限

给定表示文件(或目录)的文件变量,方法getPermissions返回一个9个字符的字符串,该字符串表示文件的权限。Linux符号表示法rw-rw-r--:

permissions = myFile.getPermissions()

同样,方法setPermissions使用相同的符号设置文件的权限:

myFile.setPermissions('rwxr-xr-x')

方法的第二个版本setPermissions使用给定的三位数字设置文件的权限,分别代表所有者,组和其他权限

myFile.setPermissions(7,5,5)

2.13 http/FTP文件

Nextflow提供HTTP / S和FTP协议的透明集成,用于将远程资源作为本地文件系统对象处理。只需将资源URL指定为文件对象的参数即可:

pdb = file('http://files.rcsb.org/header/5FID.pdb')

然后,您可以按照前面各节中的说明将其作为本地文件进行访问:

println pdb.text

上面的单行打印了远程PDB文件的内容。前面的部分提供了代码示例,显示了如何流式传输或复制文件的内容。

3 计数记录

3.1 countLines

countLines方法计算文本文件中的行数。

def sample = file('/data/sample.txt')
println sample.countLines()

名称.gz后缀结尾的文件应进行GZIP压缩并自动解压缩。

3.2 countFasta

countFasta方法对FASTA 格式文件中的记录数进行计数。

def sample = file('/data/sample.fasta')
println sample.countFasta()

名称.gz后缀结尾的文件应进行GZIP压缩并自动解压缩。

3.3 countFastq

countFastq方法对FASTQ 格式的文件中的记录数进行计数。

def sample = file('/data/sample.fastq')
println sample.countFastq()

名称.gz后缀结尾的文件应进行GZIP压缩并自动解压缩。