【问题标题】:invoking sftp through pipes: the prompt is not returned通过管道调用sftp:不返回提示
【发布时间】:2014-05-05 13:31:21
【问题描述】:

我正在尝试通过使用管道调用 SFTP 进程来使用 SFTP 复制文件(在 linux 上)。

我第一次尝试调用“ls”,但我的问题是我不知道命令的输出何时完成,所以我知道我可以发送另一个命令,并检查第一个命令的输出。

我希望 sftp 在命令完成后再次打印出它的提示,如下所示:

sftp>ls
...output...
sftp>

这样我就会知道命令何时完成。然而发生的事情是我只是得到...输出...但我从未收到新的 sftp> 提示。

我不明白这是如何在命令行中工作的,而不是在手动通过管道运行时。 this PHP article 也建议使用这种等待 sftp> 提示符的方法。

我用 java 和 ruby​​ 编写了两个实现,它们都表现出相同的行为。我小心不要使用缓冲阅读器,因为我知道这可能会导致问题,因为 sftp 不会在“sftp>”提示符后放置回车,但无济于事。

如果我在 shell echo ls | sftp <parameters> 中运行,那么它会输出并退出,而不给出新的提示。这对我来说实际上是一种“足够好”的行为。

我知道我可以在一个文件中编写我想要运行的所有命令,并通过命令参数将该文件提供给 sftp,但我发现管道方法比诉诸临时文件更可行且不那么笨重。

这里是 Java 实现:

String[] cmd = new String[]{"sftp", Config.getSshTargetUsername()
      + "@" + Config.getSshTargetHost()};

Process p = Runtime.getRuntime().exec(cmd);
InputStreamReader inp = new InputStreamReader(p.getInputStream());
OutputStreamWriter out = new OutputStreamWriter(p.getOutputStream());

out.write("ls\n");
out.flush();

StringBuilder read = new StringBuilder();
char[] buf = new char[256];
while (true) {
    int bytes = inp.read(buf);
    read.append(buf, 0, bytes);
    System.out.println(read);
    System.out.flush();
}

还有红宝石:

require 'open3'
cmd = "sftp #{username}@#{host}"
Open3.popen2e(cmd) do |stdin, stdout_err, wait_thr|
  stdin.puts "ls"
  while chr = stdout_err.getc
    putc chr
  end
end

更新:现在我意识到,如果我启动 bash,我会得到完全相同的行为。所以这是关于管道的一般情况,与 sftp 无关。

【问题讨论】:

  • 所以如果 echo ls | sftp 没问题,那么 echo "cd dir1; ls ; cd dir2 ; ls" | sftp 有什么问题。嗯..我猜我假设sftp 理解由; 字符分隔的cmds(不是一个安全的假设;-).. printf "cd dir1 \n ls \n cd dir2 \n ls \n" | sftp 怎么样。祝你好运。
  • 我尝试发送“ls;再见”,如果应用程序退出,那么我知道命令已完成并且我可以解析,遗憾的是 sftp 然后说“无效命令”。使用回车,它意味着两个命令,但我想在发送第二个命令之前等待第一个命令完成,尽管立即发送两个命令可能工作得很好。如果我不进一步了解其他可能性,我会尽快对其进行测试。

标签: ruby unix process pipe


【解决方案1】:

好的,我找到了解释和正确答案。

在 sftp 源代码中我看到了这个:

if (interactive)
    printf("sftp> ");

更早一点:

interactive = !batchmode && isatty(STDIN_FILENO);

isatty() 是关键。这样 sftp 可以检测到它是由实时用户、来自 tty 还是由另一个应用程序执行的。

所以我必须让它相信它是由实时用户启动的。一个快速的谷歌出现了这个链接: Trick an application into thinking its stdin is interactive, not a pipe

我使用script 来启动 sftp,从我的测试来看,它似乎工作得很好……所以这就是我所说的正确方式!关于它似乎在 PHP 中开箱即用的事实,我猜 PHP 在启动外部进程时正在执行该脚本技巧。

【讨论】:

    【解决方案2】:

    所以现在我放弃了(已经手动启动 sftp 正在退出使用适当的 sftp 库,我没有更多时间这样做),我现在直接使用管道启动解决方案。这样 sftp 在处理完命令后就会退出。我为每个操作启动一个新的 sftp,但它是可行的。

    在java中:

    String[] cmd = new String[] { "/bin/sh", "-c", 
          "echo ls | sftp " + Config.getSshTargetUsername() + "@" +
          Config.getSshTargetHost() };
    
    Process p = Runtime.getRuntime().exec(cmd);
    BufferedReader inp = new BufferedReader(new
          InputStreamReader(p.getInputStream()));
    String line;
    while ((line = inp.readLine()) != null) {
        System.out.println(line);
    }
    

    在红宝石中:

    require 'open3'
    cmd = 'echo ls | sftp #{username}@#{host}'
    Open3.popen2e(cmd) do |stdin, stdout_err, wait_thr|
      while l = stdout_err.readline
        puts l
      end
    end
    

    我仍然很好奇为什么我的第一个解决方案不起作用,我希望有一天有人能回答它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-18
      • 2012-12-16
      相关资源
      最近更新 更多