03-Nextflow脚本
语言基础
- Hello world、变量、列表、映射、多重赋值、条件执行、字符串、字符串插值、多行字符串
内置变量
- 脚本内隐变量、配置隐变量、处理隐式变量
闭包
正则表达式
- 字符串替换、捕捉组、删除字符串的一部分
Nextflow脚本语言是Groovy编程语言的扩展。Groovy是用于Java虚拟机的强大编程语言。Nextflow语法专门用于以声明的方式简化计算管道的编写。
Nextflow可以执行任何Groovy代码段或使用JVM平台的任何库。
关于Groovy编程语言的详细描述,请参考以下链接:
中文Groovy学习可参考https://www.w3cschool.cn/groovy/
下面是关于Nextflow脚本语言中使用的最重要的语言结构的速成课程(想精通还是要啃透英文材料)。
Warning
Nextflow使用UTF-8作为源文件和应用程序文件的默认文件字符编码。
在使用您最喜欢的文本编辑器编辑Nextflow脚本时,请确保使用UTF-8编码。
语言基础
Hello world
打印内容就像使用print或println方法一样简单。
println "Hello, World!"
两者之间的唯一区别是println方法隐式地向打印字符串追加一个新行字符。
变量
要定义一个变量,只需给它赋一个值:
x = 1
println x
x = new java.util.Date()
println x
x = -3.1499392
println x
x = false
println x
x = "Hi"
println x
列表
List对象可以通过将列表项放在方括号中来定义:
myList = [1776, -1, 33, 99, 0, 928734928763]
你可以用方括号符号访问列表中的给定项(索引从0开始):
println myList[0]
为了获得列表的长度,使用size方法:
println myList.size()
Learn more about lists:
映射(Maps)
映射用于存储关联数组或字典。它们是异构的、命名数据的无序集合:
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
了解更多关于Maps的信息:
多重赋值(Multiple assignment)
数组或列表对象可以一次性赋值给多个变量:
(a, b, c) = [10, 20, 'foo']
assert a == 10 && b == 20 && c == 'foo'
赋值操作符左边的三个变量由列表中相应的项初始化。
更多关于多重赋值的信息,请参阅Multiple assignment
。
条件执行
任何编程语言最重要的特性之一就是能够在不同的条件下执行不同的代码。最简单的方法是使用if结构:
x = Math.random()
if( x < 0.5 ) {
println "You lost."
}
else {
println "You won!"
}
字符串
字符串可以用单引号或双引号('或"字符)来定义:
println "he said 'cheese' once"
println 'he said "cheese!" again'
字符串可以用+连接:
a = "world"
print "hello " + a + "\n"
字符串插值
单引号字符串和双引号字符串之间有一个重要的区别:双引号字符串支持变量插值,而单引号字符串不支持。
在实践中,双引号字符串可以包含任意变量的值,在其名称前加上{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
多行字符串
跨越多行的文本块可以通过使用三个单引号或双引号来定义:
text = """
hello there James
how are you today?
"""
Note
Like before, multi-line strings inside double quotes support variable interpolation, while single-quoted multi-line strings do not.
在Bash/shell脚本中,用\字符结束多行字符串中的一行,可以防止用一个新的行字符将该行与下一行分隔开:
myLongCmdline = """ blastp \
-in $input_query \
-out $output_file \
-db $blast_database \
-html
"""
result = myLongCmdline.execute().text
在前面的例子中,blastp和它的-in、-out、-db和-html开关及其参数实际上是一行。
内置变量
脚本内隐变量
以下变量在脚本全局执行范围内隐式定义:
变量名称 | 描述 |
---|---|
baseDir | 主工作流脚本所在的目录(自 20.04.0 起已弃用,支持 projectDir) |
launchDir | 运行工作流的目录(需要 20.04.0 或更高版本) |
moduleDir | DSL2模块的模块脚本所在的目录,或与非模块脚本的projectDir相同的目录(需要版本20.04.0或更高版本) |
nextflow | 表示 nextflow 运行时信息的类似字典的对象(请参阅 Nextflow 元数据) |
params | 像保存在配置文件中指定的工作流参数或作为命令行选项的对象的字典 |
projectDir | 主脚本所在的目录(需要 20.04.0 或更高版本) |
workDir | 创建任务临时文件的目录 |
workflow | 表示工作流运行时信息的类似字典的对象(请参阅运行时元数据) |
配置隐变量
在Nextflow配置文件中隐式定义了以下变量:
变量名称 | 描述 |
---|---|
baseDir | 主工作流脚本所在的目录(自20.04.0以来已弃用,以支持projectDir) |
launchDir | 运行工作流的目录(需要版本20.04.0或更高版本) |
projectDir | 主脚本所在的目录(需要版本20.04.0或更高版本) |
处理隐式变量
在流程定义范围内,任务隐式变量是可用的,它允许访问当前任务配置指令。例子:
process foo {
script:
"""
some_tool --cpus $task.cpus --mem $task.memory
"""
}
在上面的代码片段中,task.cpu报告cpu指令的值,task.memory内存指令的当前值取决于工作流配置文件中给出的实际设置。
详情请参阅Process directives
闭包
简单地说,闭包是可以作为参数传递给函数的代码块。因此,您可以定义一段代码,然后像传递字符串或整数那样传递它。
更正式地说,您可以创建定义为第一类对象的函数。
square = { it * 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 documentation
默认情况下,闭包接受一个名为it的参数,但您也可以使用多个自定义命名的参数创建闭包。例如,Map.each()方法可以接受一个带两个参数的闭包,它将Map中每个key-value对的key和value绑定到这个闭包上。
这里,我们在闭包中使用了明显的变量名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
# 这里it为隐式变量,调用函数时传递的值,
# 分别为:China、India和USA
# myMap中所有键值对中的值求和
myMap.keySet().each( { result+= myMap[it] } )
println result
# 6
在Groovy documentation中了解关于闭包的更多信息
正则表达式
正则表达式是文本处理的瑞士军刀。它们为程序员提供了从字符串中匹配和提取模式的能力。
正则表达式可以通过~/pattern/语法和=~和==~操作符获得。
使用=~来检查给定的模式是否出现在字符串中的任何位置:
assert 'foo' =~ /foo/ // return TRUE
assert 'foobar' =~ /foo/ // return TRUE
使用==~检查字符串是否完全匹配给定的正则表达式模式
assert 'foo' ==~ /foo/ // return TRUE
assert 'foobar' ==~ /foo/ // return FALSE
值得注意的是,~操作符从给定的字符串创建Java Pattern
对象,而=~操作符创建Java Matcher
对象
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记录在Pattern Java documentation
字符串替换
要替换给定字符串中出现的模式,使用replaceFirst和replaceAll方法:
# 把x变量的字符串中首个ou替换成o
x = "colour".replaceFirst(/ou/, "o")
println x
// prints: color
# 把y变量中所有cheese替换成nice
y = "cheesecheese".replaceAll(/cheese/, "nice")
println y
// prints: nicenice
捕捉组
您可以匹配包含组的模式。首先使用=~操作符创建一个匹配器对象。然后,您可以索引匹配器对象来查找匹配:匹配器[0]返回一个列表,表示字符串中正则表达式的第一个匹配。第一个列表元素是匹配整个正则表达式的字符串,其余元素是匹配每个组的字符串。
下面是它的工作原理:
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
删除字符串的一部分
您可以使用正则表达式模式删除部分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'