引号和斜杠在多层中幸存

Quotes and slashes surviving multiple layers

目标 我需要有效地 运行 复制 (cp) 命令但保留显式引号。这是必需的,以便 z/OS Unix 系统服务 Korn shell 正确地将副本的目标识别为传统的 MVS 数据集。

复杂性在于此步骤是自动化过程的一部分。该命令由 Perl 生成。该 Perl 通过 ssh 在单独的 Docker 容器上执行。除了 Perl 所需的转义之外,这还增加了另一层需要解决的转义。

基本上,docker 正在做类似

的事情
perl myprogram.perl

它生成必要的 SSH 命令,将它们发送到尝试 运行 它们的大型机。当我 运行 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'"

而主机returns报错:

cp: target "//'WOODSMN.SSC.D051721.T200335.S90.CP037'" is not a directory

需要 sshpass 因为我的系统管理员拒绝打开授权用户,所以我唯一的选择是 运行 sshpass 并输入密码。包含密码暴露,我们不担心这个.

第一个命令

export _UNIX03=NO

告诉 z/OS 将 -P 选项视为 MVS 数据集控制块的指示器。也就是说,这就是我们告诉系统的地方,嘿,这是 287 个字符的固定长度,在轨道中分配等等。数据集将被假定为新的。

对于复制命令,我想z/OS复制HFS文件(基本上是一个普通的UNIX文件)

/u/woodsmn/SSC.D051721.T200335.S90.CP037

进入完全合格的 MVS 数据集

WOODSMN.SSC.D051721.T200335.S90.CP037

有时,MVS 命令假定高级限定符基本上是用户用户标识,并允许用户忽略它。在这种情况下,我明确指定了这一点。

为了让z/OS把目标当作一个数据集,需要在它前面加上两个斜杠(/),所以// 要使用完全限定的数据集,名称需要用撇号 (')

括起来

但是,为了避免在 Korn shell 中混淆,目标需要用双引号 (") 括起来。

因此,在 Perl 之间,shell 运行 在 Docker 容器(可能 bash)和接收 Korn shell 中使用我的 SSH 命令] z/OS,它没有被正确解释。

我缩小的 Perl 看起来像:


use strict;
use warnings;
sub putMvsFileByHfs;

use IO::Socket::IP;
use IO::Socket::SSL;
use IPC::Run3;
use Net::SCP;

my $SSCJCL_SOURCE_DIRECTORY = "/home/bluecost/";
my $SSCJCL_STORAGE_UNIT = "TRACK";
my $SSCJCL_PRIMARY_EXTENTS = "1";
my $SSCJCL_SECONDARY_EXTENTS = "1";
my $SSCJCL_HFS_LOCATION="/u/woodsmn";
my $SSCJCL_STAGING_HLQ = "WOODSMN";
my $COST_FILE="SSC.D051721.T200335.S90.CP037";
my $SSCJCL_USER_PW="mypass";
my $SCJCL_USER_ID="woodsmn";
my $SSCJCL_HOST_NAME="bldbmsb.boulder.mycompany.com";


my $MVS_FORMAT_OPTIONS="-P ".qq(")."RECFM=FB,LRECL=287,BLKSIZE=6027,SPACE=\("
                          .${SSCJCL_STORAGE_UNIT}
              .",\("
              .${SSCJCL_PRIMARY_EXTENTS}
              .","
              .${SSCJCL_SECONDARY_EXTENTS}
              ."\)\)".qq(");
putMvsFileByHfs(${MVS_FORMAT_OPTIONS}." ",
               $SSCJCL_SOURCE_DIRECTORY.'/'.$COST_FILE,
           ${SSCJCL_HFS_LOCATION}.'/'.$COST_FILE,
                   ${SSCJCL_STAGING_HLQ}.'.'.$COST_FILE);


# This function copys the file first from my local volume mounted to the Docker container
# to my mainframe ZFS volume. Then it attempts to copy it from ZFS to a traditional MVS
# dataset. This second part is the failinmg part.
sub putMvsFileByHfs
{
    #
    # First copy  the file from the local file system to my the mainframe in HFS form (copy to USS)
    # This part works.
    #
    my $OPTIONS = shift;
    my $FULLY_QUALIFIED_LOCAL_FILE = shift;
    my $FULLY_QUALIFIED_HFS_FILE = shift;
    my $FULLY_QUALIFIED_MVS_FILE = shift;
    RunScpCommand($FULLY_QUALIFIED_LOCAL_FILE,$FULLY_QUALIFIED_HFS_FILE);

    #
    # I am doing something wrong here 
    # Attempt to build the target dataset name. 
    #
    my $dsnPrefix = qq(\"//');
    my $dsnSuffix = qq('\");
    my $FULLY_QUALIFIED_MVS_ARGUMENT = ${dsnPrefix}.${FULLY_QUALIFIED_MVS_FILE}.${dsnSuffix};
    RunSshCommand("export _UNIX03=NO;cp ${OPTIONS}".${FULLY_QUALIFIED_HFS_FILE}." ".${FULLY_QUALIFIED_MVS_ARGUMENT});
}

# This function marshals whatever command I want to run and mostly does it. I'm not having 
# any connectivity issues. My command at least reaches the server and SSH will try to run it.
sub RunScpCommand() 
{
    my $ssh_source= $_[0];
    my $ssh_target= $_[1];
    my ($out,$err);
    my $in = "${SSCJCL_USER_PW}\n";
    my $full_command = "sshpass -p ".${SSCJCL_USER_PW}." scp ".${ssh_source}." ".${SSCJCL_USER_ID}."@".${SSCJCL_HOST_NAME}.":".${ssh_target};
    print ($full_command."\n");
    run3 $full_command,$in,$out,$err;
    print ($out."\n");
    print ($err."\n");
    return ($out,$err);
}

# This function marshals whatever command I want to run and mostly does it. I'm not having 
# any connectivity issues. My command at least reaches the server and SSH will try to run it.
sub RunSshCommand
{
    my $ssh_command = $_[0];
    my $in = "${SSCJCL_USER_PW}\n";
    my ($out,$err);
    my $full_command = "sshpass -p ".${SSCJCL_USER_PW}." ssh ".${SSCJCL_USER_ID}."@".${SSCJCL_HOST_NAME}." ".${ssh_command};
    print ($full_command."\n");
    run3 $full_command,$in,$out,$err;
    print ($out."\n");
    print ($err."\n");
    return ($out,$err);
}

由于我是 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",
   -p => $SSCJCL_USER_PW,
   "ssh", $remote_cmd,
);

run3 $ssh_cmd, ...;

但是由于您使用的是 run3,所以有更好的解决方案。您可以完全避免在本地主机上创建 shell,从而完全避免必须为其创建命令!这是通过传递对包含程序及其参数的数组的引用而不是传递 shell 命令来完成的。

use String::ShellQuote qw( shell_quote );

my @scp_cmd = (
   "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 = (
   "sshpass",
   -p => $SSCJCL_USER_PW,
   "ssh", $remote_cmd,
);

run3 \@ssh_cmd, ...;

顺便说一句,在命令行上传递密码是不安全的;机器上的其他用户可以看到它们。


顺便说一下,VAR=VAL cmd(作为单个命令)为 cmd 设置环境变量。我在上面使用了shorthand。

指定磁盘单元的参数是“TRK”而不是 TRACK,因此必须是

-P "RECFM=FB,LRECL=287,BLKSIZE=6027,SPACE=\(TRK,\(1,1\)\)"

此外,当 运行从 SSH 会话以交互方式执行这样的命令时,我从来不需要逃避括号。所以这对我有用

-P "RECFM=FB,LRECL=287,BLKSIZE=6027,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,LRECL=287,BLKSIZE=6027,SPACE=(TRK,(1,1))"

-W "seqparms='RECFM=FB,LRECL=287,BLKSIZE=6027,SPACE=(TRK,(1,1))'"

那么,只有一个命令是运行。