通过stdin传递多个文件(通过ssh)


15

我在远程主机上有一个程序,我需要自动执行该程序。该命令在同一台计算机上执行该程序,如下所示:

/path/to/program -a file1.txt -b file2.txt

在这种情况下,file1.txtfile2.txt用于在程序中完全不同的事情,所以我不能只是cat在一起。然而,在我的情况下,file1.txtfile2.txt我想要传递到程序中只有我的设备上存在的,不是我需要执行该程序的主机上。我知道我可以通过以下方式通过SSH馈送至少一个文件stdin

cat file1.txt | ssh host.name /path/to/program -a /dev/stdin -b file2.txt

但是,由于不允许将文件存储在主机上,因此我也需要一种方法来访问主机file2.txt。我认为可能是因为滥用环境变量以及创造性地使用cat和可能实现的sed,但是我对工具的了解还不足以了解如何使用它们来完成此任务。它可行吗?如何?


2
catsed不是这里的解决方案。
Slyx

也许我可以安装,但是由于代理和安全限制,我不确定是否可以摆脱它。
Green Cloak Guy

您是否在远程计算机上具有安装ssh文件夹的权限?
Slyx

1
如果您可以打开从远程计算机到本地计算机的ssh会话,那么在网络级别安装SSH文件夹就没有问题。
Slyx

你可以转发吗?您在本地和远程端都拥有哪些系统和外壳?
mosvy

Answers:


18

如果作为程序参数提供的文件是文本文件,并且您可以控制它们的内容(您知道其中没有一行),则可以使用多个here-documents:

{
    echo "cat /dev/fd/3 3<<'EOT' /dev/fd/4 4<<'EOT' /dev/fd/5 5<<'EOT'"
    cat file1
    echo EOT
    cat file2
    echo EOT
    cat file3
    echo EOT
} | ssh user@host sh

cat是一个使用文件名作为参数的示例命令。可能是:

echo "/path/to/prog -a /dev/fd/3 3<<'EOT' -b /dev/fd/4 4<<'EOT'

分别替换为每个EOT文件中都没有出现的内容。


1
这种方法很聪明!如果文件不是文本,则可以使用base64之类的文件进行编码/解码,甚至可以使用很好的uuencode进行编码/解码……非常好!
filbranden

3
请注意,几个(大多数)sh实现在此处使用临时文件来实现文档,因此,如果不允许它们在远程主机上存储文件,则该方法可能不适用于OP。
斯特凡Chazelas

2
dash/bin/sh在debian,ubuntu等中),busybox sh它们/bin/sh来自FreeBSD,yash请勿在此处使用临时文件。我实际上已经用只读chroot测试了它。当然,/dev/fd/文件必须可用-仅在现有的FreeBSD系统/dev/fd/0-2上,因此fdescfs必须安装在上/dev/fd
mosvy

1
我是ASCII信息分隔符的忠实支持者,这些分隔符旨在无障碍地分隔字段。U+001C是“信息分隔符四”(文件分隔符,FS),并且在这种情况下非常理想,尽管二进制文件可能会同时使用它。因此,我建议EOT="$(printf $'\x1c')"使用高级Shell或其他EOT="$(awk 'BEGIN{printf"%c",28}')"兼容性。将其放在适当的位置时需要用引号引起来,例如echo "$EOT$EOT$EOT"(将其加倍以降低匹配二进制文件的几率)。
亚当·卡兹

如果要使用tty,为什么不使用tty命令?除非我错过了什么-我想这可能吗?
Pryftan

14

也许不完全是您想要的...但是也许考虑通过ssh打开的管道发送压缩包?

你之前这么说:

我不允许在主机上存储文件。

您可能没有一个可写的主目录或其他方便的位置来长期存储文件,但是我想说您不太可能没有任何可写的位置,即使只有一个临时位置tmpfs可供您使用特定的连接。

许多程序(甚至libc例程)都需要可写的/tmp,因此很可能会为您提供一个可写的。

然后,您可以使用脚本将tarball解压缩到一个临时目录中,运行程序并通过ssh连接进行清理。

就像是:

$ tar cf - file1.txt file2.txt |
  ssh host.name '
      set -e
      tmpdir=$(mktemp -d -t tmp.XXXXXXXXXX)
      cleanup () { rm -rf "$tmpdir"; }
      trap cleanup EXIT
      cd "$tmpdir"
      tar xf -
      /path/to/program -a file1.txt -b file2.txt
  '

这可能需要格外注意文件路径,并且需要考虑一些特殊情况(对其进行测试),但是一般方法应该可行。

如果没有可写目录,则可能的方法是修改program以将tarball作为单个输入并将其内容解压缩到内存中。例如,如果program是Python脚本,则使用内置tarfile模块将轻松完成类似的工作。


3
即使没有麻烦解决它,我还是决定或多或少地将文件写入/tmp/并直接使用。
Green Cloak Guy

2
tarball方法具有一些优点,因为您使用单个ssh连接(无需测试成功/失败),并且多个并发运行不太可能相互运行(使用和/或删除/ tmp中的文件意味着分开运行) 。)但是我认为这是从ssh获得的最好的东西,因为它现在已经存在。祝好运!
filbranden

3
@GreenCloakGuy所以,毕竟,您可以在服务器上存储文件。请相应地修改问题。
mosvy

1
@mosvy 可以,是的,但是不想。问题在于,“是否可以在不存储任何文件的情况下做到这一点”,答案似乎是“也许,但就我而言不是”
Green Cloak Guy

5

如果可以设置TCP侦听器(可以是更高的端口),则可以使用第二个SSH会话通过来建立第二个输入源nc

例:

服务器(~/script.bash)上有以下脚本:

#!/usr/bin/env bash
cat "$1" | tr a 1
nc localhost "$2" | tr '[:lower:]' '[:upper:]'

本地有这两个文件:

$ cat a 
aaa
aaa
aaa
$ cat b
bbb
bbb
bbb

现在,首先启动第二个源($serv是服务器):

ssh "$serv" nc -l -p 2222 -q1 <b &

并正确运行命令:

$ ssh "$serv" ./script.bash - 2222 <a
111
111
111
BBB
BBB
BBB
[1]+  Done                    ssh "$serv" nc -l -p 2222 -q1 < b

2

它是在可写评论中建立的/tmp,因此只需事先复制其中一个文件即可:

scp -p file2.txt host.name:/tmp/
ssh host.name "/path/to/program -a /dev/stdin -b /tmp/file2.txt && rm /tmp/file2.txt" < file1.txt

成功运行后,这也会清除复制的文件(如果无论成功与否,都将其更改&&为a ;,但是请注意,将丢失退出值)。


如果不可接受,我建议对其进行修补/path/to/program或包装,以将两个文件从单个输入流中分离出来,例如:

awk 'FNR == 1 && NR > 1 { printf "%c%c%c", 28, 28, 28 } 1' file1.txt file2.txt \
  | ssh host.name /path/to/tweaked_program

它使用ASCII信息分隔符四(文件分隔符,FS)并将其增加三倍,以便我们将二进制文件同时包含该字符串的机会降到最低。tweaked_program然后,您将使用分隔符分割输入,然后将两个保存的文件作为变量进行操作。

当然,如果您使用一种具有库来处理tarball的语言,那么一种更安全,更清洁的方法就是将tar这样的代码跨入管道ssh

tar -zpc file1.txt file2.txt |ssh host.name /path/to/tweaked_program

然后您tweaked_program将解压缩并打开档案,将每个文件保存到不同的变量,然后对变量运行原始program的逻辑。


1

如果允许通过转发端口ssh,并且可以访问wget远程计算机和busybox本地计算机,则可以执行以下操作:

mkdir /tmp/test; cd /tmp/test
echo 1st_file > 1st_file
echo 2nd_file > 2nd_file

busybox httpd -f -p 127.0.0.1:11080 &
ssh USER@HOST -R 10080:127.0.0.1:11080 '
        cat <(wget -q -O- http://localhost:10080/1st_file) \
            <(wget -q -O- http://localhost:10080/2nd_file)
'
kill $!

cat以带有两个文件参数的示例程序为例)。

只有通过转发端口的能力-R才是必不可少的-您可以使用其他方法来代替http,而不必使用http。如果您 netcat支持-d-N选项:

nc -Nl localhost 11001 < 1st_file &
nc -Nl localhost 11002 < 2nd_file &
ssh USER@HOST -R 10001:localhost:11001 -R 10002:localhost:11002 '
        cat <(nc -d localhost 10001) <(nc -d localhost 10002)

<(...)如果远程计算机上的登录外壳不是ksh-或bash-like,则可能存在替换进程替换的方法。

总而言之,这并不是太好-更好地了解确切的系统/ shell / config /权限(您尚未提供)可以提供更智能的解决方案。


-1

更新:这实际上不起作用,ssh对stdin和stdout / stderr的含义有一个非常确定的概念,并且实际上并不允许篡改stderr读取它。由于无法使用,我将在几天后删除该答案。感谢您进行的非常有趣的讨论!!!


不幸的是有没有直接这样做,因为一个伟大的方式,ssh客户端将只通过三个文件描述符(stdinstdoutstderr)到服务器,它没有规定通过额外的文件描述符(这将是这个特定用例有帮助)

(还要注意,SSH协议提供了传递附加文件描述符的规定,只有ssh客户端没有实现使用该功能的方法。从理论上讲,使用补丁程序扩展客户端就足以暴露此功能。)

完成目标内容的一种简便方法是使用文件描述符2(stderr文件描述符)传递第二个文件。就像是:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/stderr \
      <file1.txt 2<file2.txt

这应该可行,如果program尝试写入stderr ,可能会导致问题。您可以通过在运行程序之前重新处理远程端的文件描述符来解决此问题。您可以移至file2.txt文件描述符3并重新打开stderr以镜像stdout:

$ ssh remote.name \
      /path/to/program -a /dev/stdin -b /dev/fd/3 \
      '3<&2' '2>&1' \
      <file1.txt 2<file2.txt

我还没有真正测试过(在电话上打字),但至少在理论上我希望它能工作。让我知道是否由于某种原因而没有。干杯!
filbranden

那根本行不通。甚至没有被ssh入侵。AFAIK stderr不使用单独的通道,而是使用某种特殊的消息。但是,是的,能够以管道或未命名的unix域套接字的方式访问ssh通道(而不是必须进行套接字转发)会很好,但这似乎不可能以任何形式或形式出现。
mosvy

@mosvy,远程命令上的fd 0、1和2将是3个不同的管道。数据通过ssh连接中多路复用的3个不同通道传输,但这些通道是单向的(对于fd 0,是从客户端到服务器,对于1和2是从服务器到客户端),所以即使在管道是双向的系统上,也不会工作。在Linux上,以只读模式打开/ dev / stderr会在管道的另一端提供数据,因此它也将永远无法工作。
斯特凡Chazelas

请注意,最后一个假定上remote.name的用户的登录壳是类似Bourne(3<&2是Bourne shell操作者,和sshd的运行用户的登录壳解释由客户端的命令发送)
斯特凡Chazelas

2
@StéphaneChazelas否,stdin / stdout / stderr使用了一个ssh通道,并且stderr数据通过SSH_MSG_CHANNEL_EXTENDED_DATA类型设置为的消息传输SSH_EXTENDED_DATA_STDERR。你可以阅读整个事情在这里
mosvy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.