WDL - 内置函数

版本

和其他编程语法一样,WDL也存在不同版本间的语法差异,因此在使用WDL进行流程撰写时,需要明确参考的版本规范
draft-3
v1.0
v1.1 截止202301,cromwell还不支持该版本。
所以后文语法如无特殊标准,均基于 WDL v1.0 版本。

通过之前的介绍,我们了解 wdl的整体框架组成、任务调度过程中涉及的执行逻辑、如何投递一个简单的任务以及我们在编写WDL流程过程中涉及的数据结构。基于wdl本身的使用场景(串接一个个数据处理的任务,而不是自己进行数据处理),所以至此,我们已经原则上可以实现各种pipeline的开发了。当然这种可以实现本身是不优雅的,因为我们不可能对所有简单的数据处理,都单独撰写一个task任务,一方面会让代码冗长,另一方面,这些冗余的操作会影响代码的执行逻辑,也会降低执行阶段的执行效率。
所以今天我们来了解一些wdl本身支持的内置函数,让我们开发的流程显得尽可能优雅一些。

读取文件

pipeline构建完成后,我们可以像流程投递中介绍的,每次分析不同的样本,我们都单独构建生成一个输入的input.json文件。但是这有两个问题,一个是input里面会包含一些非样本的配置信息重复生成很繁琐,另一个方面是手动构造json格式文件并不是一个人类有好的工作。
所以我们这时候,就需要做两部分,首先是将每次分析会更换的样本信息独立开,通过一个独立的文件而不是input.json进行提供,另一方面就是结合我们上游数据来源确定一个更人类友好的样本信息配置文件(如果是机器生成可以是json,如果是人准备可以是tsv)。
这时候我们就需要在wdl内部进行一些文件读取的操作,wdl提供了几个文件读取的参数,read_tsv、read_json、read_int、read_lines、read_map、read_object、read_float、read_string

read_tsv

read_tsv()函数用于读取tsv文件,返回一个二维数组 Array[Array[String]]。可以直接使用二维属组的方式进行数值的获取。但是使用tsv有一个比较麻烦的问题,就是不支持title的识别,所以只能按index获取数据。下面是一个示例:

这是我们需要在wdl中进行读取的文件:输入的文本文件.tsv

1
2
3
4
5
sample_id	fq1Path	fq2Path
****059 /V350201891_L01_51_1.fq.gz /V350201891_L01_51_2.fq.gz
****571 /V350201891_L01_44_1.fq.gz /V350201891_L01_44_2.fq.gz
****851 /V350201891_L01_54_1.fq.gz /V350201891_L01_54_2.fq.gz
****853 /V350201891_L01_56_1.fq.gz /V350201891_L01_56_2.fq.gz

那么在wdl中,我们可以通过如下方式进行数据的读取:

1
2
3
4
5
6
7
8
# 解析tsv并转换成 Array[Array[String]]
Array[Array[String]] batch_info = read_tsv("输入的文本文件.tsv")
# 按行遍历输入的数据
scatter (sample in batch_info){
# 针对每行的数据,通过列索引获取对应的信息。
sample_id =sample[0]
fq1=sample[1]
fq2=sample[2]

read_json

wdl 中可以通过read_json函数读取json文件。读取后,根据json的数据类型,会保存成对应的wdl结构数据。json类型和wdl类型对应关系如下:
| JSON Type | WDL Type |
| ——— | —————- |
| object | Map[String, ?] |
| array | Array[?] |
| number | Int or Float |
| string | String |
| boolean | Boolean |
| null | ??? |
| Pair | object |
| Map | object |
| Struct | object |

读取json文件的示例如下:
input_file.json

1
2
3
4
5
6
7
8
9
10
11
12
{"cancer":[{
"LibID":"1C-Lib-1",
"LibData":[["FastqA1","FastqA2"],["FastqB1","FastqB2"],["FastqC1","FastqC2"],["FastqD1","FastqD2"]]
},{
"LibID":"1C-Lib-2",
"LibData":[["L2FastqA1","L2FastqA2"],["L2FastqB1","L2FastqB2"],["L2FastqC1","L2FastqC2"]]
}],
"normal":[{
"LibID":"1N-Lib-1",
"LibData":[["NFastqA1","NFastqA2"],["NFastqB1","NFastqB2"],["NFastqC1","NFastqC2"]]
}]
}

解析脚本.wdl

1
2
3
4
5
6
7
8

Object All_Sample = read_json(input_file.json)
# All_Sample.cancer 获取json中 cancer对应的列表,并通过scatter对这个列表进行遍历,依次处理每个文库
scatter ( singleLin in All_Sample.cancer){
# 对每个文库(json的字典),转换成 object(wdl的结构),通过属性获取文库对应的值(LibID或LibData)
String lib = singleLin.LibID
# 将 LibData的值[["FastqA1","FastqA2"],["FastqB1","FastqB2"],["FastqC1","FastqC2"],["FastqD1","FastqD2"]],直接保存成一个wdl的对象结构式 Array[Array[String]]
Array[Array[String]] FastqList = singleLin.LibData

其他

  • read_map 读取两列的tsv文件,并将两列 TSV 解释为 Map[String, String]

    1
    Map[String, String] mapping = read_map( File/String )
  • read_lines 按行读取,并把多行存储到一个字符串构成的数组中.

    1
    Array[String] matches = read_lines( File/String )
  • read_float、read_string 读取一个文件路径,预期只有一行和一个值(对应需要读取的类型)

输出文件

write_lines

给定与 Array[String] 兼容的内容,这会将每个元素写入文件中自己的行。使用换行符 \n 字符作为行分隔符。

1
2
3
4
5
6
7
8
9
10
11
task example {
Array[String] array = ["first", "second", "third"]
command {
./script --file-list=${write_lines(array)}
}
}

# 生成文件格式如下:
first
second
third

write_tsv

给定与 Array[Array[String]] 兼容的内容,这会写入数据结构的 TSV 文件。

1
2
3
4
5
6
7
8
9
10
task example {
Array[String] array = [["one", "two", "three"], ["un", "deux", "trois"]]
command {
./script --tsv=${write_tsv(array)}
}
}

# 生成文件文件格式如下:
one\ttwo\tthree
un\tdeux\ttrois

write_json

给定任何类型的数据,write_json 会写入对应结构的json文件。对应关系参见read_json()定义中的表格。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
task example {
input {
Map[String, String] map = {"key1": "value1", "key2": "value2"}
}
command {
./script --map=${write_json(map)}
}
}

#生成的json文件示例:
{
"key1": "value1",
"key2": "value2"
}

write_objects

给定任何 Array[Object] ,这将写出一个 2+ 行、n 列的 TSV 文件,其中包含每个对象的属性和值。

1
2
3
4
5
6
7
8
9
10
11
task test {
input {
Array[Object] in
}
command <<<
/bin/do_work --obj=~{write_objects(in)}
>>>
output {
File results = stdout()
}
}

Attribute 属性 Value 价值
key_1 “value_1”
key_2 “value_2”
key_3 “value_3”

上述格式的object对象生成文件后,格式如下:

1
2
key_1\tkey_2\tkey_3
value_1\tvalue_2\tvalue_3

统计大小

统计文件容量 size

1
2
3
4
5
6
7
8
9
10
# 统计文件的大小
Float input_file_size = size(input_file)

# 统计文件大小指定单位(支持 kB、MB、GB、TB)
Float created_file_size_in_KB = size("created_file", "K") # 0.022

# 输入的是数组,则返回属组中所有文件大小的总和
Float size(Array[File], [String])

# 字符串处理

统计属组长度大小

1
2
3
4
5
6
7
Array[Int] xs = [ 1, 2, 3 ]
Array[String] ys = [ "a", "b", "c" ]
Array[String] zs = [ ]

Integer xlen = length(xs) # 3
Integer ylen = length(ys) # 3
Integer zlen = length(zs) # 0

字符串相关

判断是否被定义 defined

如果参数是未设置的可选值,则此函数将返回 false 。在所有其他情况下它将返回 true 。

字符串基于正则进行替换 sub

sub(input, pattern, replace) 给定 3 个字符串参数 input 、 pattern 、 replace ,此函数将使用replace的呢日哦那个替换 input 中匹配 pattern 的任何匹配项 。 pattern 应该是一个正则表达式。正则表达式评估的详细信息将取决于运行 WDL 的执行引擎。

1
2
3
4
5
6
String chocolike = "I like chocolate when it's late"

String chocolove = sub(chocolike, "like", "love") # I love chocolate when it's late
String chocoearly = sub(chocolike, "late", "early") # I like chocoearly when it's early
String chocolate = sub(chocolike, "late$", "early") # I like chocolate when it's early
}

添加字符串前缀 prefix

给定一个 String 和一个 Array[X],其中 X 是基本类型, prefix 函数返回一个字符串数组,该数组由输入数组的每个元素组成,并添加了指定的前缀字符串为前缀。例如:

1
2
3
4
5
Array[String] env = ["key1=value1", "key2=value2", "key3=value3"]
Array[String] env_param = prefix("-e ", env) # ["-e key1=value1", "-e key2=value2", "-e key3=value3"]

Array[Integer] env2 = [1, 2, 3]
Array[String] env2_param = prefix("-f ", env2) # ["-f 1", "-f 2", "-f 3"]

文件相关

获取文件基本名称 basename

basename 根据输入文件名(如输入文件名)命名输出文件名(剔除目录信息)的字符串。如果还给它一个字符串(后缀)作为辅助参数,该函数将尝试从文件名剔除后缀后返回剩余的任何内容。

1
2
3
4
File input_file = "/Users/chris/input.bam"
String base = basename(input_file) # 返回 input.bam

String stripped = basename(input_file, ".bam") # 返回 input

数值相关

数组的筛选提取

  • select_first(Array[X?])
    给定一个可选值数组, select_first 将选择第一个被定义的值并返回它。请注意,这是一项运行时检查,要求至少存在一个定义的值。
  • select_all(Array[X?])
    给定一个可选值数组, select_all 将仅选择那些已定义的元素。

查找符合正则的文件 glob

Glob 可用于定义可能包含零个、一个或多个文件的输出。因此,glob 函数返回 File 输出的数组:

1
2
3
output {
Array[File] output_bams = glob("*.bam")
}

  • 使用场景:
    输出文件有多个,文件具体名称不确定,但是知道文件命名的规则(正则表达式)
  • 使用方法:
    采用 glob 表达式,用 array 方式存储多个输出文件
  • 价值:
    输出结果支持通配符匹配,简化 WDL 编写,采用数组方式,方便并发处理

类型转换

浮点数转整型

  • Floor(Float)
    向下舍入到下一个较小的整数
  • ceil(Float)
    向上舍入到下一个更大的整数
  • round(Float)
    根据标准舍入规则舍入到最接近的整数

zip 构建pair类型

给定任意两个对象类型, zip 函数以 Pair 对象的形式返回这些对象类型的点积。

1
2
3
4
5
6
Pair[Int, String] p = (0, "z")
Array[Int] xs = [ 1, 2, 3 ]
Array[String] ys = [ "a", "b", "c" ]
Array[String] zs = [ "d", "e" ]

Array[Pair[Int, String]] zipped = zip(xs, ys) # i.e. zipped = [ (1, "a"), (2, "b"), (3, "c") ]

标准输出:stdout, stderr

  • stdout()函数用于捕获command中命令生成的标准输出。
  • stderr()函数用于捕获command中命令生成的标准报错。
    stderr比stdout更常用,更多用于捕获warning信息
    1
    2
    String out_info = stdout()
    String err_info = stderr()
-------------本文结束感谢您的阅读-------------