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.3

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 .csv extension in the working directory that are collected into an array by the glob function.

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.3

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.3

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.3

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.3

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.example1 will resolve to aFile
  • optional_output.example2 will resolve to None
  • optional_output.file_array will 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"
  }
}