Windows 下的 CMS/TSO 管道 - 第三部分 用户阶段
IBM 管道(Windows 版)和 PowerShell 的实现。
引言
CMS/TSO 管道是一款运行在 IBM 大型机上的产品。在 z/VM 系统上,它运行在 CMS 下。在 z/OS 系统上,它运行在 TSO 下。顾名思义,数据通过一系列内置或用户定义的阶段进行传递。在此过程中,数据可以被修改或从管道中删除。您可能会想,这有什么新颖之处。PipeLines 的不同之处在于它支持并发并行管道,并且数据可以被路由进出管道。
系统要求
虽然 PipeLib 类库可以从 .Net 程序访问,但我认为在 PowerShell 下运行它会更有用。我所有的测试都是通过 PowerShell 完成的,任何我提供的管道很可能都是 PowerShell 脚本。因此,我建议的最低系统要求如下:
PowerShell V1.0
.Net Framework V2
调用用户阶段
用户阶段是通过内置的 PS 阶段调用的。该阶段将用户编写的阶段名称以及用户希望传递给该用户阶段的任何参数作为参数。用户阶段的基本大纲如下:
#BasicStage.ps1
1)param ([object] $usp, [String] $parms)
2) ... Perform any needed setup, such as parsing the passed parameter string ...
3) usp-peekto 'inputRec'
4) while ($rc -eq 0) {
5) ... process the record
6) usp-output 'outputRec'
if ($rc -eq 0) {
7) usp-ReadTo
8) usp-PeekTo 'inputRec'
}
}
第 1 行是唯一强制性的行。在每个使用任何用户阶段命令的脚本中都必须存在。第 3-8 行设置了保持数据记录顺序所需的基本数据流。这并非强制执行,用户可以自由编写最适合应用程序的代码。第 1 行定义的 $usp 变量是连接脚本与 PipeLib 类库的接口。管道用户命令会检查此变量的存在性和有效性。
管道用户命令
下面是管道用户阶段中可用的命令列表。
- Run-CallPipe
- 此命令以并行方式执行管道,与当前管道并行。用户阶段在新的管道运行时被挂起。当 CallPipe 命令结束时,用户阶段将恢复。定义新管道的字符串将作为参数传递给此命令。
示例:run-callpipe $pipe 或 run-callpipe "pipe definition" - Run-AddPipe
- 此命令以并行方式执行管道,与当前管道并行。与 CallPipe 命令不同,用户阶段将继续运行。定义管道的字符串将作为参数传递给此命令。
示例:run-addpipe $pipe 或 run-addpipe "pipe definition" - Usp-PeekTo
- 此命令读取下一个传递给该阶段的记录,但不会消耗它。用于接收记录的变量名将作为参数传递。
示例:usp-peekto "varname" - Usp-ReadTo
- 此命令读取并消耗下一个传递给该阶段的记录。用于接收记录的变量名是可选参数。
示例:usp-readto "varname" 或 usp-readto - Usp-Output
- 此命令将数据写入当前选定的输出流。输出数据将以变量或文字字符串的形式传递。
示例:usp-output $line 或 usp-output "literal data string" - Usp-Select
- 此命令选择用于输入和/或输出的数据流。当用户阶段启动时,主输入和输出流是默认选定的。此命令需要两个参数:
- 要处理的流类型:“Input”或“Output”
- 流编号或标识符。
示例:usp-select "both" 0, usp-select "input" 1, usp-select "input" "stream-name" - Usp-Commit
- 此命令设置该阶段要设置的提交级别。新的提交级别是必需的参数。
示例:usp-commit 0 - Usp-NoCommit
- 此命令阻止用户阶段自动提交。
示例:usp-nocommit - Usp-ScanRange
- 此命令扫描数据字符串以查找位置范围参数。该命令接受四个参数:
- 指示位置参数是必需的还是可选的,并假定以下值之一:“Required”或“Optional”。
- 一个用于接收生成的范围位置令牌的变量名。
- 一个用于接收位置解析后剩余数据字符串的变量名。
- 第四个参数是可选的。它指定要解析的数据。如果省略,则使用传递给用户阶段的参数字符串。
示例:usp-scanrange "required" "token" "rest" "fs - f1", usp-scanrange "optional" "token" "." - Usp-GetRange
- 此命令从数据记录中提取由先前 ScanRange 指定的数据。该命令接受四个参数:
- 由先前调用 ScanRange 生成的令牌。
- 用于接收提取数据的变量类型。它可以假定以下值之一:“VARiable”或“STEM”。
- 用于接收提取数据的变量名。
- 要从中提取数据的记录。
示例:usp-getrange $token "var" "varname" $data, usp-getrange $token "stem" "stemname" $data - Usp-ScanString
- 此命令从数据字符串中提取下一个单词或分隔的字符串。该命令接受三个参数:
- 用于接收字符串的变量名。
- 用于接收任何剩余数据的变量名。
- 要从中提取字符串的数据字符串。此参数是可选的。如果省略,则使用传递给用户阶段的参数字符串。
示例:usp-scanstring "string" "rest" $data, usp-scanstring "string" "." - Usp-Short
- 此命令将当前选定的输入流缩短到当前选定的输出流。
示例:usp-short - Usp-Sever
- 此命令断开当前选定的输入或输出流。单个参数指定要断开的流。如果被断开的流是通过 CallPipe 或 AddPipe 连接器重定向的,则会尝试恢复原始连接。
示例:usp-sever "input" 或 usp-sever "output" - Usp-StreamNum
- 此命令获取指定流的流编号。该命令接受两个参数:
- 要处理的流类型:“Input”或“Output”
- 流编号或标识符。此参数是可选的。如果省略,则使用当前选定的输入或输出流。
示例:usp-streamnum "input" 1, usp-streamnum "output" "id" - Usp-StreamState
- 此命令查询指定流的状态。该命令接受一个或两个参数:
- 当第一个参数是“Input”或“Output”时,第二个参数(如果指定)是要查询的流的编号或 ID。默认是当前选定的流。
- 当第一个参数是“Summary”时,返回代码 0 表示至少有一个输入流和一个输出流已连接。
- 当第一个参数是“All”时,第二个参数是要接收每个流对状态的变量名。
示例:usp-streamstate "input" 1, usp-streamstate "all" "varname" - Usp-MaxStream
- 此命令返回所选流类型的最大流编号。该命令接受一个参数:
- 要处理的流类型:“Input”或“Output”
示例:usp-maxstream "input", usp-maxstream "output" - Usp-AddStream
- 此命令将一个未连接的流添加到用户阶段。该命令接受两个参数:
- 要处理的流类型:“Input”、“Output”或“Both”。
- 流标识符。此参数是可选的。如果省略,则使用 Usp-MaxStream 获取添加流的编号。
示例:usp-addstream "input" "strmid", usp-addstream "both"
AddPipe 和 CallPipe
上述命令中的 CallPipe 和 AddPipe 需要特别注意。这些命令将并行管道插入到现有的管道线中,并且根据为并行管道指定的选项,它可能与现有管道交互,也可能不交互。CallPipe 和 AddPipe 命令的管道定义语法比 Run-Pipe 命令多一个元素。
┌─<─endchar┐
>>─┬AddPipe─┬┬─────────────────────────┬┬─────────┬┴┤ PipeLine├┴─><
└CallPipe┘│ ┌─<─────────────────┐ │├stagesep─┤
└(─┴┬┬STAGESEP──┬xorc─┬┴)─┘└endchar──┘
│└SEPerator─┘ │
├ENDchar xorc─────┤
├ESCape xorc──────┤
├NAME─pipename────┤
└TRACE────────────┘
Group PipeLine
├─┬───────────┬─>
└┤connector├┘
┌─<─stagesep───────────────────────────────────────────────┐
>─┴┬────────────────────────────────────────────────────────┬┴─>
├┬────────────┬┬───────────────┬stagename─┬─────────────┬┤
│└┤LabelGroup├┘└(─┬───┬TRACE─)─┘ └stageoptions─┘│
│ └NO─┘ │
└┤LabelGroup├────────────────────────────────────────────┘
>─┬───────────┬─┤
└┤connector├┘
Group Connector
├─*─┬───────────────────────────┬:──┤
│ ┌.*────────────┐│
└.─┬INPUT──┬┼──────────────┼┘
└OUTPUT─┘└.─┬*─────────┬┘
├streamnum─┤
└streamid──┘
Group LabelGroup
├─┬────────────────┬─┤
├label:──────────┤
└label.streamid:─┘
- 选项
- STAGESEP xorc
分隔符 - 指定用于分隔管道阶段的字符。它可以指定为单个字符或字符的十六进制数字。默认分隔符是竖线“|”。
- ENDchar xorc
- 指定分隔管道中各个管道的字符。它可以指定为单个字符或字符的十六进制数字。没有默认值。
- ESCape xorc
- 定义一个转义字符,用于覆盖对具有 PipeLine 解析器特殊意义的字符的处理。转义字符后面的任何字符都将被视为普通字符。没有默认值。
- NAME pipename
- 指定一个名称以关联到此管道。
- TRACE
- 为管道打开跟踪。
- 管道
- stagesep
- 管道的阶段由 STAGESEP 字符分隔。默认是“|”。
- endchar
- 如果管道中有多个管道,则用 ENDCHAR 分隔它们。
- 否
- 如果为管道启用了跟踪,则“NO TRACE”将为此管道阶段关闭跟踪。
- TRACE
- 为此管道的此阶段打开跟踪。如果以“NO”关键字作为前缀,则为该阶段关闭跟踪。
- stagename
- 要执行的阶段的名称。
- stageoptions
- 提供指定 stagename 所需的任何选项。
- *
- 将此标识为连接器定义的开始。
- 输入
- 此连接器应用于用户阶段的输入流。如果连接器是管道的第一个阶段,则用户阶段的输入流将被定向到 CallPipe 或 AddPipe 管道的第一个阶段。如果连接器是管道的最后一个阶段,则 CallPipe 或 AddPipe 管道的输出将被定向到用户阶段的输入。
- 输出
- 此连接器应用于用户阶段的输出流。如果连接器是管道的第一个阶段,则用户阶段的输出流将被定向到 CallPipe 或 AddPipe 管道的第一个阶段。如果连接器是管道的最后一个阶段,则 CallPipe 或 AddPipe 管道的输出将被定向到用户阶段的输出。
- *
- 此连接器应用于当前选定的输入或输出流。
- streamnum
- 受影响的用户阶段数据流的流编号。
- 受影响的用户阶段数据流的流 ID。
- :
- 标记连接器定义的结束。
- label
label.streamid - 一个标签,用于标识阶段的其他输入和/或输出数据流。标签的第一次出现称为标签定义。它建立了一个连接点,在该连接点,其他管道中的其他阶段可以从该连接点接收数据或向其发送数据。每次后续使用相同的标签都称为标签引用。使用标签引用来为阶段定义其他输入和输出流。要使用标签引用,请指定一个仅包含标签且不包含 stagename 的阶段。
streamid 为流分配一个符号名称。通过向标签添加标识符来命名流。立即在标签后面写一个句点 (.),最多四个字母数字字符,或包含至少一个字母字符的字母数字字符组合。流标识符后面必须紧跟一个冒号,中间不能有空格。
考虑管道 "A|Z|D",其中 "Z" 是一个发出 CallPipe 或 AddPipe 命令的用户阶段。以下示例演示了在CallPipe使用时,连接器对管道中数据流的影响。
CallPipe: B|C *.Input:|B|C B|C|*.Output: *.Input:|B|C|*.Output:
A A A A
│ B └─B │ B └─B
DataFlow: Z │ Z │ Z │ Z │
│ C │ C C C
D D D─┘ D─┘
Restorable: NA Yes Yes Yes
(1) (2) (3) (4)
- 没有连接器时,数据在每个管道中从一个阶段流向下一个阶段。
- A 的输出流被定向到 B 的输入。Z 的输入流被断开。
- C 的输出流被定向到 D 的输入。Z 的输出流被断开。
- A 的输出流被定向到 B 的输入。C 的输出流被定向到 D 的输入。Z 的输入和输出流被断开。
以下示例演示了在AddPipe使用时,连接器对管道中数据流的影响。
AddPipe: B|C *.Input:|B|C B|C|*.Output: *.Output:|B|C B|C|*.Input:
A A A A A B
│ B └─B │ B │ │
DataFlow: Z │ Z │ Z │ Z Z─ C
│ C │ C ┌─C └─B │
D D D D │ D
C
Restorable: NA No No Yes Yes C
(1) (2) (3) (4) (5)
AddPipe: *.Input:|B|C|*.Output: *.Input:|B|C|*.Input:
A A
└─B └─B
DataFlow: Z │ │
C C
D─┘ Z─┘
│
D
Restorable: No No
(6) (7)
AddPipe: *.Output:|B|C|*.Input: *.Output:|B|C|*.Output:
A A
┌─┐ ┌─B │ ┌─B
DataFlow: │ Z │ │ Z │ │
│ └─┘ C └─┘ C
└─────┘ ┌───┘
D D
Restorable: Yes No
(8) (9)
- 没有连接器时,数据在每个管道中从一个阶段流向下一个阶段。
- A 的输出流被定向到 B 的输入。Z 的输入流被断开。
- C 的输出流被定向到 D 的输入。Z 的输出流被断开。
- Z 的输出流连接到 B 的输入。D 的输入流被断开。
- C 的输出流连接到 Z 的输入。A 的输出被断开。
- A 的输出流被定向到 B 的输入。C 的输出流被定向到 D 的输入。Z 的输入和输出流被断开。
- A 的输出流连接到 B 的输入。C 的输出流连接到 Z 的输入。
- Z 的输出流连接到 B 的输入。C 的输出流连接到 Z 的输入。A 的输出和 D 的输入流被断开。一切都有可能发生停滞。
- Z 的输出流连接到 B 的输入。C 的输出流连接到 D 的输入。
一个示例
下面是一个调用用户阶段的管道示例。
#AddPipeDemo
$data = "B999 1323 FFFF BA82 1A43 20DD"
$pipe = 'var data'+
'|split'+
'|PS userstage'+
'|stem Stem1'
run-pipe $pipe
上述管道调用的用户阶段是:# AddPipeDemo userstage
1) param ([object] $usp, [String] $parms)
# Read parameter file
2) run-addpipe '< addpipe.conf | *.input:' # Connect input to file
3) usp-nocommit # Disable automatic commit
4) usp-readto 'line' # Read first line of file
while ($rc -eq 0) { # Process all lines
if ($line[0] -ne '#') { # Ignore any comments
usp-scanstring 'sortOrder' '.' $line
write-host "The selected sort order is: $sortOrder"
}
usp-readto 'line' # Read next line
}
$order = 'fa-ff'
if ($sortOrder -eq 'Lower') {
$order = '0a-0f'
}
5) usp-sever 'input' # Re-instate input file
6) usp-commit 0
7) $pipe = '*.input:' + # connect to output of stage preceding caller
'|xlate 1-* A-F '+$order+ # Translate A to F
'|sort' + # Sort the records
'|xlate 1-* '+$order+' A-F'+ # Restore original characters A to F
'|*.output:' # connect to input of stage following caller
8) run-callpipe $pipe
- 用户阶段所需的强制性参数定义。
- 启动并行管道。此管道的输出被定向到此阶段的主输入。之前的输入流被保存,以便稍后可能重新连接。
- 这可以阻止用户阶段执行自动提交。所有阶段都以某个提交级别开始。对于用户阶段,它是 -1。大多数内置阶段都以某个级别开始,验证阶段的状态,然后提交到级别 0。处于最低提交级别的阶段在高于提交级别的阶段之前运行。所有内置阶段最终都将处于级别 0。因此,使用此命令,该阶段将保持在级别 -1,直到它显式提交到更高的级别。通常,用户阶段的任何 IO 请求都会导致自动提交到级别 0。
- 读取第一个输入记录。请注意,此读取将消耗记录。无需担心记录顺序,因为记录永远不会离开此阶段。
- 在处理完所有记录后,我们将恢复原始输入流。
- 在这里,我们提交到级别 0。其他阶段已经提交到级别 0,并正在等待此阶段加入它们。一旦所有阶段都处于级别 0,它们都有资格运行。
- 定义要执行的管道。
- 执行管道。
另一个例子
下面的用户阶段示例接收包含二进制数据的记录,并将它们以十六进制格式转储到屏幕上。
#Dump.ps1
param ([object] $usp, [string] $parms)
usp-scanstring 'width' '.'
if ($width -eq '16') {
$text = ' 1-* 45 '
} else {
$width = '32'
$text = ' 1-16 83 17-* 101 '
}
$pipe = '(end ?) *.Input:'+
'|deblock fixed '+$width+
'|f:fanout'+
'|copy'+
'|s:spec set (#1=1; #2=8; #3=0)'+
' while (#1<length() && #3<4) do'+
' print (c2x(substr(record(), #1, 4))) (#2, 8)'+
' set (#1+=4; #3+=1; #2+=9)'+
' done'+
' set (#2=45; #3=0)'+
' while (#1<length() && #3<4) do'+
' print (c2x(substr(record(), #1, 4))) (#2, 8)'+
' set (#1+=4; #3+=1; #2+=9)'+
' done'+
' select 1 '+$text+
' pad 0 print (d2x(#0)) 1.4 r'+
' set (#0+='+$width+')'+
'|*.output:'+
'? f:'+
'|xlate 00-1f . 80-9f .'+
'|s:'
run-callpipe $pipe
此阶段(调用时带有宽度参数 16)产生的输出示例。0000 3f523f4e 54465320 20202000 02080000 ?R?NTFS .....
0010 00000000 003f0000 3f003f00 043f0000 .....?..?.?..?..
0020 00000000 3f003f00 11583f03 00000000 ....?.?..X?.....
0030 3f7f0c00 00000000 3f024000 00000000 ?⌂......?.@.....
参考文献
z/VM V5R3.0 Pipelines Reference
z/VM V5R2.0 CMS Pipelines User's Guide
CMS/TSO Pipelines Runtime Library Distribution
作者帮助文件