以下示例是使用管道的一种经典方法。在管道的右侧,我们有一个“简单”实用程序,它从标准输入(管道)读取数据并以相反的顺序打印到标准输出:
{ cat <</EOF
Hello
World
/EOF
} | rev
显然结果是:
olleH
dlroW
现在我想通过管道将数据输入到命令/实用程序中,这是 shell 的一部分,here-doc:
{ cat <</EOF
Hello
World
/EOF
} | ksh -x <</EOF
rev
/EOF
这不起作用,没有错误消息,什么也没有,管道数据丢失,因为 ksh 将 stdin 重定向到here-doc。
我尝试了某种重定向:
set -x
{ cat <</EOF
Hello
World
/EOF
} | { exec 4<&0; ksh -x <</EOF; exec 0<&4 4<&-; }
rev <&4
/EOF
但这也不起作用,它在 rev 命令上显示错误,显然 stdin 到 fd4 的重定向没有传递到 ksh 的子进程:
+ exec
+ 4<& 0
+ ksh -x
+ cat
+ 0<< \/EOF
rev <&4
/EOF
+ 0<< \/EOF
Hello
World
/EOF
+ rev
+ ksh[1]: 4: cannot open [Ungültiger Dateideskriptor]
+ exec
+ 0<& 4 4<& -
我被困在这里了。
我的实际应用程序要复杂得多:在管道的左侧,我将通过 ssh 连接到源服务器,可以选择通过 sudo 连接到该服务器上的其他用户,并运行 tar 以通过管道传输一些数据文件。在管道的右侧,我必须 ssh 到目标服务器,可以选择 sudo 到不同的用户,最后运行 tar 以从管道接收数据。
顺便说一句:如果我不使用管道右侧的此处文档,则一切正常:
{ cat <</EOF
Hello
World
/EOF
} | ksh -xc "rev"
即使使用 ssh 加 sudo 加 tar 也可以正常工作。出于某种原因,我希望在右侧有一个 shell 的此处文档。正如您所看到的,ksh 是该应用程序中我最喜欢的 shell。
根据要求:我原来的声明是这样的
ssh $SSHOPT $INST_FILE_USER@$INST_FILE_HOST "cd $IDIR && { tar -cvf - $IFIL && echo RCSRC0 >&2 || echo RCSRC1 >&2; }" 2>$TMPFILE.err.src | ssh $SSHOPT $TRG_USER@$TRG_HOST "cd $TDIR && { tar -xmvf - && echo RCTRG0 || echo RCTRG1; }" 1>$TMPFILE.out.trg 2>&1
到目前为止一切正常。正如您所看到的,执行了最少的错误检查,这允许识别 tar 成功。现在我必须添加 sudo 功能,这意味着在 ssh 的 shell 和 cd 命令之间我必须添加一些代码来切换用户。难道真的是sudo,会用到吗?可能是 pbrun 命令,因为客户可能更喜欢 pbrun。如果使用此处文档而不是 1 个怪物语句,则可以更灵活地处理这些不同的场景。
5月21日更新: 我尝试遵循迄今为止给出的建议,但我仍然陷入困境。让我们看一下下面给出的答案,它对我来说效果很好:
echo yup | ksh -c 'exec 4<&0; ksh -c "rev <&5" 5<&4'
puy
如果我开始使用 ssh 或将 ssh 与 sudo 结合使用,我的重定向示例将不再起作用:
echo yup | ssh admin@trg14 ksh -c 'exec 4<&0; ksh -c "rev <&5" 5<&4'
bash: 4: Bad file descriptor
echo yup | ssh admin@trg14 sudo -n -u trg4 ksh -c 'exec 4<&0; ksh -c "rev <&5" 5<&4'
bash: 4: Bad file descriptor
我尝试了 ssh 和 sudo 组合的 2 个场景:第一个是使用 here-doc 作为 ksh 的输入:
场景一:
echo yup | ssh admin@trg14 sudo -n -u trg4 -- ksh<</EOF
whoami && whoami && rev && whoami
/EOF
trg4
trg4
trg4
到目前为止,这是有效的。注意:所有 4 个命令均以用户 trg4 身份运行,由于此处文档,rev 未按预期通过管道获得任何输入。
因此我尝试实现下面建议的解决方案:here-doc 将作为 ksh 的参数输入:
场景2:
echo yup | ssh admin@trg14 sudo -n -u trg4 -- ksh -c "$(cat<</EOF
whoami && whoami && rev && whoami
/EOF
)"
trg4
admin
puy
admin
rev 的输出符合预期,但:whoami 向我们展示了在这个版本/实现中只有第一的命令以 sudo 用户身份运行,其余命令将以 ssh 登录用户身份运行!在 rev 的情况下没有问题,但如果您需要访问或写入数据库文件,您将因权限问题而失败。
从这 2 个示例得出的结论:场景 1 可能是在正确的 sudo 用户下运行所有命令的正确途径,但它需要解决 stdin 重定向的问题,以便能够使用管道。
任何帮助表示赞赏!
答案1
一个简单的解决方法是将 here-doc 脚本作为-c
参数传递给内壳:
{ cat <<'/EOF'
Hello
World
/EOF
} | ksh -c "$(<<'/EOF'
rev
# you can freely use " inside this script
/EOF
)"
会给
olleH
dlroW
这也适用于zsh
.更改$(<<
为$(cat<<
应该让它工作任何壳。
您还可以摆弄重定向,前提是您在命令级别而不是使用以下命令进行重定向exec
:
... | ksh 4<&0 <<'/EOF'
rev <&4
/EOF
在这种情况下,您还可以使用 交换回here-doc脚本中的fds exec 5<&4 4<&0 <&5 5<&-
,但恕我直言,所有这些都是毫无价值的复杂化。
ksh 中的脚本级重定向
第三个示例中的问题可以简化为更简单的测试用例:
$ echo yup | ksh -c 'exec 4<&0; ksh -c "rev <&4"'
ksh: 4: cannot open [Bad file descriptor]
但:
$ echo yup | dash -c 'exec 4<&0; ksh -c "rev <&4"'
puy
这是因为将在使用以下命令打开的任何 fd 上ksh
设置 close-on-exec 标志 ( )O_CLOEXEC
脚本级重定向(例如使用exec 4</path/to
或exec 4<&0
)。这是由标准:
如果
exec
在没有命令或参数的情况下指定,并且使用关联的重定向语句打开任何编号大于 2 的文件描述符,则未指定当 shell 调用另一个实用程序时这些文件描述符是否保持打开状态。
这可以通过做额外的事情来解决命令级重定向:
echo yup | ksh -c 'exec 4<&0; ksh -c "rev <&5" 5<&4'
puy
使用ksh93
(但不使用mksh
早于R57
[1] 的)简单地重新复制 fd 本身就可以工作:
$ echo yup | ksh93 -c 'exec 4<&0; ksh -c "rev <&4" 4<&4'
puy
[1]2019年3月发布,修复于此犯罪。
答案2
[你应该问一个不同的问题不同的问题,而不是堆砌这个]
使用 [command] 参数运行 ssh
当您ssh
使用[command]
参数运行时,如 中ssh user@host foo bar baz
,会发生的情况是它的参数将由空间到单个参数中,该参数将通过-c
交换机传递到远程计算机上用户的登录 shell(如果该 shell 的/bin/sh
,/bin/sh -c 'foo bar baz'
将运行)。
现在,您的示例中会发生什么:
echo yup | ssh admin@trg14 ksh -c 'exec 4<&0; ksh -c "rev <&5" 5<&4'
ssh 的参数将被连接成一个字符串:
ksh -c exec 4<&0; ksh -c "rev <&5" 5<&4
并且删除机器上的 shell 将首先运行
ksh -c exec 4<&0
进而
ksh -c "rev <&5" 5<&4
作为两个单独的命令,当然第二个命令中的 fd 4 不指向任何内容,因为它仅在第一个命令中重定向。