问题描述
目标 我需要有效地运行复制 (cp) 命令,但保留显式引号。这是必要的,以便 z/OS Unix 系统服务 Korn shell 将副本的目标正确识别为传统的 MVS 数据集。
复杂的是,此步骤是自动化流程的一部分。该命令由 Perl 生成。 Perl 是通过 ssh 在单独的 Docker 容器上执行的。除了 Perl 所需的转义之外,这增加了另一层需要解决的转义。
基本上,docker 正在做类似的事情
<table style="width: 337px; margin-left: auto; margin-right: auto" border="1">
<tbody>
<tr>
<td style="width: 90px" colspan="9">
<table style="width: 329px" border="1">
<tbody>
<tr class="hobbies">
<td style="width: 105.6875px" onmouSEOver="changeImg('skating')">
Skating
</td>
<td style="width: 110.3125px" onmouSEOver="changeImg('swimming')">
Swimming
</td>
<td style="width: 130px" onmouSEOver="changeImg('cooking')">
Cooking
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr style="text-align: center">
<td style="width: 90px" colspan="9">
<!-- Change the src attribute onmouSEOver -->
<img id="imgtochange" src="skating.jpg" alt="skating" />
</td>
</tr>
</tbody>
</table>
<br />
生成必要的 SSH 命令,将它们发送到试图运行它们的大型机。当我运行 Perl 脚本时,它会生成命令
perl myprogram.perl
然后主机返回错误:
sshpass -p passwd ssh woodsmn@bldbmsb.boulder.mycompany.com export _UNIX03=NO;cp -P "RECFM=FB,LRECL=287,BLKSIZE=6027,SPACE=\(TRACK,\(1,1\)\)" /u/woodsmn/SSC.D051721.T200335.S90.CP037 "//'WOODSMN.SSC.D051721.T200335.S90.CP037'"
需要sshpass,因为我的系统管理员拒绝打开授权用户,所以我唯一的选择是运行sshpass并输入密码。密码暴露被包含在内,我们不担心这个。
第一条命令
cp: target "//'WOODSMN.SSC.D051721.T200335.S90.CP037'" is not a directory
告诉 z/OS 将 -P 选项视为 MVS 数据集控制块的指示符。也就是说,这是我们告诉系统的地方,嘿,这是一个固定长度的 287 个字符,在轨道中分配等。数据集将被假定为新的。
对于复制命令,我希望 z/OS 复制 HFS 文件(基本上是一个普通的 UNIX 文件)
export _UNIX03=NO
进入完全合格的MVS数据集
/u/woodsmn/SSC.D051721.T200335.S90.CP037
有时 MVS 命令假定一个高级限定符基本上是用户 userid,并允许用户忽略它。在这种情况下,我已经明确指定了这一点。
要让 z/OS 将目标视为数据集,需要在它前面加上两个斜杠 (/),所以 // 要使用完全限定的数据集,名称需要用撇号 (') 括起来
但是,为了避免在 Korn shell 中混淆,目标需要用双引号 (") 括起来。
因此,不知何故,在 Perl、Docker 容器内运行我的 SSH 命令的 shell(可能是 bash)和 z/OS 上的接收 Korn shell 之间,它没有被正确解释。
我缩小的 Perl 看起来像:
WOODSMN.SSC.D051721.T200335.S90.CP037
请原谅以上任何 Perl 的不当之处,因为我是 Perl 的新手,尽管感谢善意的建设性指针。
解决方法
首先,让我们构建要传递给程序的值。我们稍后会担心构建 shell 命令。
my @OPTIONS = (
-P => join(',',"RECFM=FB","LRECL=287","BLKSIZE=6027","SPACE=($SSCJCL_STORAGE_UNIT,($SSCJCL_PRIMARY_EXTENTS,$SSCJCL_SECONDARY_EXTENTS))",),);
my $FULLY_QUALIFIED_LOCAL_FILE = "$SSCJCL_SOURCE_DIRECTORY/$COST_FILE";
my $FULLY_QUALIFIED_HFS_FILE = "$SSCJCL_HFS_LOCATION/$COST_FILE";
my $FULLY_QUALIFIED_MVS_FILE = "$SSCJCL_STAGING_HLQ.$COST_FILE";
my $FULLY_QUALIFIED_MVS_ARGUMENT = "//'$FULLY_QUALIFIED_MVS_FILE'";
简单易行。
现在是构建要执行的命令的时候了。关键是要避免尝试同时进行多个级别的转义。首先构建远程命令,然后然后构建本地命令。
use String::ShellQuote qw( shell_quote );
my $scp_cmd = shell_quote(
"sshpass",-p => $SSCJCL_USER_PW,"scp",$FULLY_QUALIFIED_LOCAL_FILE,"$SSCJCL_USER_ID\@$SSCJCL_HOST_NAME:$FULLY_QUALIFIED_HFS_FILE",);
run3 $scp_cmd,...;
my $remote_cmd =
'_UNIX03=NO ' .
shell_quote(
"cp",@OPTIONS,$FULLY_QUALIFIED_HFS_FILE,$FULLY_QUALIFIED_MVS_ARGUMENT,);
my $ssh_cmd = shell_quote(
"sshpass","ssh",$remote_cmd,);
run3 $ssh_cmd,...;
但是由于您使用的是 run3
,所以有一个更好的解决方案。您可以完全避免在本地主机上创建 shell,从而完全避免必须为其创建命令!这是通过传递对包含程序及其参数的数组的引用而不是传递 shell 命令来完成的。
use String::ShellQuote qw( shell_quote );
my @scp_cmd = (
"sshpass",);
run3 \@scp_cmd,);
my @ssh_cmd = (
"sshpass",);
run3 \@ssh_cmd,...;
顺便说一句,在命令行中传递密码是不安全的;机器上的其他用户可以看到它们。
顺便说一下,VAR=VAL cmd
(作为单个命令)为 cmd
设置了环境变量。我使用了上面的速记。
指定磁盘单元的参数是“TRK”而不是“TRACK”,所以必须是
-P "RECFM=FB,LRECL=287,BLKSIZE=6027,SPACE=\(TRK,\(1,1\)\)"
此外,当从 SSH 会话以交互方式运行这样的命令时,我从来不必逃避括号。所以这对我有用
-P "RECFM=FB,SPACE=(TRK,(1,1))"
然后,错误
cp: target "//'WOODSMN.SSC.D051721.T200335.S90.CP037'" is not a directory
表示 cp
知道它必须复制多个源文件,因此它要求最终路径名是目录。似乎确认 cp
没有在远程主机上运行,而是在您的本地 shell 上运行(正如有人指出的那样是由于没有转义分号造成的)。并且您的本地 UNIX 不理解 z/OS 特定的MVS 数据集符号//'your.mvs.data.set'
。
除了导出 _UNIX03=NO
,您还可以替换
-P "RECFM=FB,1))"
与
-W "seqparms='RECFM=FB,1))'"
那么,只需要运行一个命令。