Python中的逐行远程数据传输

2 投票
4 回答
568 浏览
提问于 2025-04-16 04:37

我一直在玩一个叫做 subprocess 的模块,目的是把输入文件中的每一行逐个发送给一个通过下面的命令创建的进程。

ssh -t -A $host 'remote_command'

这个 remote_command 需要从它的标准输入(STDIN)接收一行,然后对这行进行处理,并且会一直循环,直到标准输入关闭或者到达文件结束(EOF)。

为了实现这个目标,我之前的做法是:

process = subprocess.Popen("ssh -t -A $host 'remote_command'",
                           shell=True,
                           stdin=subprocess.PIPE)
for line in file('/tmp/foo'):
  process.stdin.write(line)
  process.stdin.flush()
process.stdin.close()

但是我发现,这种方法不够稳妥,因为经常会出现 remote_command 提前结束,没能处理完所有内容(虽然有时候同样的代码又能顺利执行,没有问题)。

当我使用另一种非常相似的方法时,情况也是一样的:

process = subprocess.Popen("ssh -t -A $host 'remote_command'",
                           shell=True,
                           stdin=file('/tmp/foo'))

所以我的问题是:我该如何确保输入文件中的每一行都能被发送、接收,并且在远程机器上处理完,直到结束呢?

4 个回答

0

与其使用子进程,不如试试 paramiko 这样的工具。

不过无论你选择哪种方式,如果你的连接在你发送完所有数据之前就断开了,你可以捕捉到这个错误,这样你就知道需要重试了。如果进程意外结束,你应该能读取到这个进程的退出代码。

1

你已经做了很多工作来确保所有输入都能发送到你的子进程了。在我看来,你的第二个例子比第一个要好。你可以做的就是检查一下你子进程的返回代码。

return_code = p.wait()

你的远程命令在成功完成时应该返回0,如果发生错误则返回其他非零的数字。

2

如果这个...

process = subprocess.Popen("ssh -t -A $host 'remote_command'",
                           shell=True,
                           stdin=subprocess.PIPE)
for line in file('/tmp/foo'):
    process.stdin.write(line)
    process.stdin.flush()
process.stdin.close()

...是你整个程序的内容,那它可能不会(一定)正常工作。

虽然最后调用 process.stdin.close() 会确保所有数据在你的程序结束前都已经发送给 ssh 进程,但这并不能保证 ssh 进程已经把所有数据都发送到网络上,所以可能还有一些数据没有发送。

不幸的是,由于 ssh 进程是你程序的子进程,当你的程序结束时,ssh 进程会收到一个 SIGHUP 信号,这会立即终止它,可能在它还没发送完所有数据之前就被杀掉了。

只要 remote_command 在遇到文件结束符(EOF)时能正常结束,那就没问题。你可以选择让 ssh 进程忽略 SIGHUP,继续在后台运行,方法是...

process = subprocess.Popen("nohup ssh -t -A $host 'remote_command'", ...)

...或者让你的程序等到 ssh 进程完成后再结束,通过在程序末尾添加...

process.wait()

...来实现。


更新

经过进一步检查,发现一个进程只有在它的控制终端(tty)结束时才会收到 SIGHUP,而不是它的父进程。

这可能与 -t 选项有关,它在远程主机上创建了一个新的控制终端,而这个终端在它启动的子进程完成之前就退出了。

在这种情况下,你可能需要...

process = subprocess.Popen("ssh -t -A $host 'nohup remote_command'", ...)

...或者尝试不使用 -t 选项。

撰写回答