Task Outputs
Declaring and evaluating task output parameters
The output section contains declarations that are exposed as outputs of the task after the successful execution of the instantiated command. An output declaration must be initialized, and its value is evaluated only after the task's command completes successfully, enabling any files generated by the command to be used to determine its value.
Example: outputs_task.wdl
version 1.2
task outputs {
input {
Int t
}
command <<<
printf ~{t} > threshold.txt
touch a.csv b.csv
>>>
output {
Int threshold = read_int("threshold.txt")
Array[File]+ csvs = glob("*.csv")
Boolean two_csvs = length(csvs) == 2
}
}Example input:
{
"outputs.t": 5
}
Example output:
{
"outputs.threshold": 5,
"outputs.two_csvs": true
}
Test config:
{
"exclude_outputs": ["outputs.csvs"]
}After the command is executed, the following outputs are expected to be found in the task execution directory:
- A file called "threshold.txt", which contains one line that consists of only an integer and whitespace.
- One or more files (as indicated by the
+postfix quantifier) with the.csvextension in the working directory that are collected into an array by theglobfunction.
See the WDL Value Serialization section for more details.
ยงFile, Directory, and Optional Outputs
File and Directory outputs are represented as path strings.
A common pattern is to use a placeholder in a string expression to construct a file name as a function of the task input. For example:
Example: file_output_task.wdl
version 1.2
task file_output {
input {
String prefix
}
command <<<
printf "hello" > ~{prefix}.hello
printf "goodbye" > ~{prefix}.goodbye
>>>
output {
Array[String] basenames = [basename("~{prefix}.hello"), basename("~{prefix}.goodbye")]
}
}Example input:
{
"file_output.prefix": "foo"
}
Example output:
{
"file_output.basenames": ["foo.hello", "foo.goodbye"]
}In the preceding example, if prefix were specified as "foobar", then "~{prefix}.out" would be evaluated to "foobar.out".
Another common pattern is to use the glob function to define outputs that might contain zero, one, or many files.
Example: glob_task.wdl
version 1.2
task glob {
input {
Int num_files
}
command <<<
for i in {1..~{num_files}}; do
printf ${i} > file_${i}.txt
done
>>>
output {
Array[File] outfiles = glob("*.txt")
Int last_file_contents = read_int(outfiles[num_files-1])
}
}Example input:
{
"glob.num_files": 3
}
Example output:
{
"glob.last_file_contents": 3
}
Test config:
{
"exclude_outputs": ["glob.outfiles"]
}Relative paths are interpreted relative to the execution directory, whereas absolute paths are interpreted in a container-dependent way. Absolute paths that reference locations outside the task's execution directory may not be supported by all execution engines, particularly in environments where access to the container's filesystem is restricted.
Example: relative_and_absolute_task.wdl
version 1.2
task relative_and_absolute {
command <<<
mkdir -p my/path/to
printf "something" > my/path/to/something.txt
>>>
output {
String something = read_string("my/path/to/something.txt")
# The following may or may not work depending on what the execution engine
# supports.
#
# File bashrc = "/root/.bashrc"
}
requirements {
container: "ubuntu:focal"
}
}Example input:
{}
Example output:
{
"relative_and_absolute.something": "something"
}All File and Directory outputs are required to exist when the output section is evaluated (i.e., when the output values are created), otherwise the task will fail. However, an output may be declared as optional (e.g. File?, Directory?, or Array[File?]), in which case the value will be undefined if the file does not exist.
Example: optional_output_task.wdl
version 1.2
task optional_output {
input {
Boolean make_example2
}
command <<<
printf "1" > example1.txt
if ~{make_example2}; then
printf "2" > example2.txt
fi
>>>
output {
File example1 = "example1.txt"
File? example2 = "example2.txt"
Array[File?] file_array = ["example1.txt", "example2.txt"]
Int file_array_len = length(select_all(file_array))
}
}Example input:
{
"optional_output.make_example2": false
}
Example output:
{
"optional_output.example2": null,
"optional_output.example1": "example1.txt",
"optional_output.file_array": ["example1.txt", null],
"optional_output.file_array_len": 1
}
Test config:
{
"exclude_outputs": ["optional_output.example1", "optional_output.file_array"]
}Executing the above task with make_example2 = true will result in the following outputs:
optional_output.example1will resolve to aFileoptional_output.example2will resolve toNoneoptional_output.file_arraywill resolve to[<File>, None]
The execution engine may need to "de-localize" File and Directory outputs. For example, if the WDL is executed on a cloud instance, then the outputs must be copied to cloud storage after execution completes successfully.
When a File or Directory is de-localized, its name and contents (including subdirectories) are preserved, but not necessarily its local path. Any hard- or soft-links shall be resolved into regular files/directories.
For example, if a task produces the following Directory output:
dir/
- a # a file, 10 MB
- b -> a # a softlink to 'a'
Then, after de-localization, it would be:
dir/
- a # a file, 10 MB
- b # another file, 10 MB
If this were then passed to the Directory input of another task, it would contain two independent files, dir/a and dir/b, with identical contents.
WDL does not have any built-in way to specify that an output Directory should only contain a subset of files in the local directory, so a common pattern is to create an output directory with the desired structure and soft-link the desired output files into that directory.
task output_subset {
command <<<
for i in 1..10; do
touch file${i}
done
# we only want the first three files in the output directory
mkdir -p outdir/subdir
ln -s file1 outdir
ln -s file2 outdir
ln -s file3 outdir/subdir
>>>
output {
Directory outdir = "outdir"
}
}