2.3. 表达式#

如果需要对输入参数进行处理,请添加需求 InlineJavascriptRequirement. 然后,在任何可以合法引用参数的地方,您都可以提供一 JavaScript 片段,让 CWL 运行程序计算其值。

重要

JavaScript 表达式应当仅在绝对必要时使用。要处理文件名、扩展名、路径等,请优先考虑能否使用 basenamenamerootnameextFile 内置属性。对此,请参阅优良习惯一览

expression.cwl#
#!/usr/bin/env cwl-runner
cwlVersion: v1.2
class: CommandLineTool
baseCommand: echo

requirements:
  InlineJavascriptRequirement: {}

inputs: []
outputs:
  example_out:
    type: stdout
stdout: output.txt
arguments:
  - prefix: -A
    valueFrom: $(1+1)
  - prefix: -B
    valueFrom: $("/foo/bar/baz".split('/').slice(-1)[0])
  - prefix: -C
    valueFrom: |
      ${
        var r = [];
        for (var i = 10; i >= 1; i--) {
          r.push(i);
        }
        return r;
      }

由于此工具不需要任何 inputs(输入),因此运行的时候可以使用一个几乎为空的作业文件:

empty.yml#
{}

empty.yml 的内容是一个空 JSON 对象的描述。JSON 对象描述是用大括号 {} 围起来的,因此,表示空对象仅需一对空括号就够了。

然后我们可以运行 expression.cwl:

运行 expression.cwl#
$ cwltool expression.cwl empty.yml
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.2.20260411152607
INFO Resolved 'expression.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/expression.cwl'
INFO [job expression.cwl] /tmp/ywd57jq_$ echo \
    -A \
    2 \
    -B \
    baz \
    -C \
    10 \
    9 \
    8 \
    7 \
    6 \
    5 \
    4 \
    3 \
    2 \
    1 > /tmp/ywd57jq_/output.txt
INFO [job expression.cwl] completed success
{
    "example_out": {
        "location": "file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/output.txt",
        "basename": "output.txt",
        "class": "File",
        "checksum": "sha1$a739a6ff72d660d32111265e508ed2fc91f01a7c",
        "size": 36,
        "path": "/home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/output.txt"
    }
}INFO Final process status is success
$ cat output.txt
-A 2 -B baz -C 10 9 8 7 6 5 4 3 2 1

请注意,如上例所示,requirements(要求)可通过映射语法来提供:

requirements:
  InlineJavascriptRequirement: {}

也可以作为一个数组,每个元素均用 - 标记(本例中只有一个 class: InlineJavascriptRequirement ),就和描述额外命令行参数的语法一样。

requirements:
  - class: InlineJavascriptRequirement

哪里可以使用 JavaScript 表达式?

JavaScript 表达式如同参数引用一样,只能用于某些特定字段,即:

2.3.1. 通过 expressionLib 使用外部库和内联 JavaScript 代码#

InlineJavascriptRequirement 这一需求支持 expressionLib 属性,可以让用户加载外部 JavaScript 文件或提供内联 JavaScript 代码。

添加到 expressionLib 属性的条目将由 CWL 运行程序的 JavaScript 引擎进行语法分析。这可以用来纳入外部文件中的代码,或创建可在 CWL 文件其他部分调用的 JavaScript 函数。

备注

版本 1.0 到 1.2 的 CWL 标准中写道,在 CWL 表达式中唯一有效的 JavaScript 版本是 ECMAScript 5.1 . 也就是说,在 CWL 文件中纳入或写入的所有 JS 代码都必须符合该版本。

例如,我们可以使用 InlineJavascriptRequirements, 在 expressionLib 下编写内联 JavaScript 函数,让它能在 CWL 文件其他部分调用:

hello-world-expressionlib-inline.cwl#
cwlVersion: v1.2
class: CommandLineTool
requirements:
  - class: InlineJavascriptRequirement
    expressionLib:
      - |
        /**
         * Capitalize each word passed. Will split the text by spaces.
         * For instance, given "hello world", it returns "Hello World".
         *
         * @param {String} message - The input message.
         * @return {String} the message with each word with its initial letter capitalized.
         */
        function capitalizeWords (message) {
          if (message === undefined || message === null || typeof message !== 'string' || message.trim().length === 0) {
            return '';
          }
          return message
            .split(' ')
            .map(function (token) {
              return token.charAt(0).toUpperCase() + token.slice(1);
            })
            .join(' ');
        }

baseCommand: echo

inputs:
  message:
    type: string

arguments: [$( capitalizeWords(inputs.message) )]

outputs: []

运行此 CWL 工作流将调用这个 JavaScript 函数,使得 echo 命令以大写首字母排印输入的“报文”:

运行 hello-world-expressionlib-inline.cwl.#
$ cwltool hello-world-expressionlib-inline.cwl --message "hello world"
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.2.20260411152607
INFO Resolved 'hello-world-expressionlib-inline.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/hello-world-expressionlib-inline.cwl'
INFO [job hello-world-expressionlib-inline.cwl] /tmp/epe47qgu$ echo \
    'Hello World'
Hello World
INFO [job hello-world-expressionlib-inline.cwl] completed success
{}INFO Final process status is success

我们来将 capitalizeWords 函数移动到外部文件 custom-functions.js, 并将它导入我们的 CWL 文件:

custom-functions.js#
/**
 * Capitalize each word passed. Will split the text by spaces.
 * For instance, given "hello world", it returns "Hello World".
 *
 * @param {String} message - The input message.
 * @return {String} the message with each word with its initial letter capitalized.
 */
function capitalizeWords (message) {
  if (message === undefined || message === null || typeof message !== 'string' || message.trim().length === 0) {
    return '';
  }
  return message
    .split(' ')
    .map(function (token) {
      return token.charAt(0).toUpperCase() + token.slice(1);
    })
    .join(' ');
}
hello-world-expressionlib-external.cwl#
cwlVersion: v1.2
class: CommandLineTool
requirements:
  - class: InlineJavascriptRequirement
    expressionLib:
      - { $include: custom-functions.js }

baseCommand: echo

inputs:
  message:
    type: string

arguments: [$( capitalizeWords(inputs.message) )]

outputs: []

文件 custom-functions.js 通过 $include: custom-functions.js 语句纳入到这个 CWL 文件里,使这个 JavaScript 文件中定义的函数和变量可在 CWL 文件其他部分使用。

运行 hello-world-expressionlib-external.cwl.#
$ cwltool hello-world-expressionlib-external.cwl --message "hello world"
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.2.20260411152607
INFO Resolved 'hello-world-expressionlib-external.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/hello-world-expressionlib-external.cwl'
INFO [job hello-world-expressionlib-external.cwl] /tmp/go_fwpz0$ echo \
    'Hello World'
Hello World
INFO [job hello-world-expressionlib-external.cwl] completed success
{}INFO Final process status is success

最后请注意,CWL 文件中可以同时运用内联和外部 JavaScript 代码。下面是最后一个例子,这里我们向 expressionLib 属性中加入新函数 createHelloWorldMessage, 调用外部文件 custom-functions.js 中的 capitalizeWords 函数。

hello-world-expressionlib.cwl#
cwlVersion: v1.2
class: CommandLineTool
requirements:
  - class: InlineJavascriptRequirement
    expressionLib:
      - { $include: custom-functions.js }
      - |
        /**
         * A merely illustrative example function that uses a function
         * from the included custom-functions.js file to create a
         * Hello World message.
         *
         * @param {Object} message - CWL document input message
         */
        var createHelloWorldMessage = function (message) {
          return capitalizeWords(message);
        };

baseCommand: echo

inputs:
  message:
    type: string

arguments: [$( createHelloWorldMessage(inputs.message) )]

outputs: []
运行 hello-world-expressionlib.cwl.#
$ cwltool hello-world-expressionlib.cwl --message "hello world"
INFO /home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/envs/latest/bin/cwltool 3.2.20260411152607
INFO Resolved 'hello-world-expressionlib.cwl' to 'file:///home/docs/checkouts/readthedocs.org/user_builds/common-workflow-languageuser-guide-zh-hans/checkouts/latest/src/_includes/cwl/expressions/hello-world-expressionlib.cwl'
INFO [job hello-world-expressionlib.cwl] /tmp/66198n5k$ echo \
    'Hello World'
Hello World
INFO [job hello-world-expressionlib.cwl] completed success
{}INFO Final process status is success

备注

$include 语句可以用来添加位于本地或远程的文件,并且接受相对路径和绝对路径。详情请参见 CWL 规约中关于 $include 的内容。