【问题标题】:Writing to stdout, also print the inputs supplied like a real terminal写入标准输出,也像真正的终端一样打印提供的输入
【发布时间】:2013-12-02 06:14:04
【问题描述】:

让我们有一个简单的 Java 应用程序,其中我有以下代码来扫描用户输入:

Scaner scan = new Scanner(System.in);
System.out.print("Enter the value: ");
int value = scan.nextInt();
System.out.println("You entered: " + value); 

当我手动运行它时,正如预期的那样,我看到了我输入的内容,示例运行将是:

Enter the value: 3
You entered: 3

但是,当我有一个包含内容的文件时

3

我想通过这个将程序标准输出重定向到一个文件:

java Test < INPUT > OUTPUT

我得到的都是cat OUTPUT:

Enter the value: You entered: 3

由于输入的值没有显示在标准输出上,这是正确的,它们不在标准输出上,我知道,我看到它们是因为我手动将它们输入到我的终端。但是当我像这样重定向标准输出时,如何让它们也被看到?

编辑: 我可以看到代码正在进行read(0, 系统调用。

$ (strace -ff -e trace=read -e read 2>&1 java Test < INPUT) | grep "read(0,"
[pid  7297] read(0, "3\n", 8192)        = 2

有没有更好的拦截方法?

编辑 2: 我的意思是,当我使用java &lt; INPUT &gt; OUTPUT 运行它时,输出应该如下所示

Enter the value: 3
You entered: 3

【问题讨论】:

  • 检测输入是否为TTY(终端),如果不是,手动打印输入数据。也许在 Java 中检测不是那么容易,请参阅 stackoverflow.com/questions/1403772/… 。 (在大多数其他语言中,此功能是内置的。)
  • 我不想修改提供的代码,它们来自外部。
  • 如果您不想更改提供的代码,还有哪些适合您的选项?
  • 除了更改提供的代码之外的任何东西。我可以编写另一个程序、脚本来运行给定的代码,并以不同的方式处理输入/输出重定向。也许会检测到java Test 何时需要输入,然后将 INPUT 的内容写入 OUTPUT 和 STDIN 到文件中,但我想知道是否还有另一种更简单的方法,我觉得我在发明轮子。
  • 检测java Test 何时需要输入是不可能的。您可以查看它打印的内容,并从中推断出它最可能需要输入的时间。

标签: java linux shell io-redirection


【解决方案1】:

您不能修改提供的代码,但可以修改启动它的方式。

我提供的是一个假设 System.in 发生变化的 hack。我做了什么:

1) 我用你发布的代码用单一的main 方法制作了一个测试程序。我启动了它:

$ java -cp dist/Test.jar test.Test <input 
Enter the value: You entered: 3

2)我写了一个java程序

  1. 重定向输入流 (System.in)
  2. 启动 test.Test.main

代码:

package redirecter;

import java.io.IOException;
import java.io.InputStream;

public class Redirecter {

    public static void main(String[] args) {
        final InputStream oldIn = System.in;
        InputStream hackedIn = new InputStream() {

            @Override
            public int read() throws IOException {
                int b = oldIn.read();
                System.out.write(b);
                return b;
            }

            @Override
            public int read(byte[] b, int off, int len) throws IOException {
                int res = oldIn.read(b, off, len);
                System.out.write(b, off, res);
                return res;
            }
        };
        System.setIn(hackedIn);

        test.Test.main(args);
    }
}

3) 我用新方式启动旧代码:

$ java -cp dist/Test.jar:../Redirecter/dist/Redirecter.jar redirecter.Redirecter <input 
Enter the value: 3
You entered: 3

提供的代码未经编辑;它只是通过棘手的方式启动的。所有参数和 jvm 选项都已保存。如果你有多个入口点,你可以使用反射而不是简单地调用test.Test.main(args);

坏点:如果有人在提供的代码中编辑System.in,黑客就会被破坏。但在您使用标准输入之前,它似乎是安全的。

【讨论】:

  • 虽然它是java特有的,但它并没有修改给定的代码,它给了我一个想法,我愿意以通用的形式实现这个泛型并尽快发布,谢谢。
【解决方案2】:

编写另一个程序,我们称之为驱动程序,在驱动程序中调用new Process(),在进程中执行这个程序,将标准输入和标准输出连接到驱动程序。一旦进程写入Enter new value:,让驱动程序从输入文件等发送值。让驱动程序打印其输入和输出。

另一个选项是驱动程序在strace(Linux 特定)内运行进程,监控read(0, ...) 调用。

这是 Perl (rtrace.pl) 中的一个解决方案,它使用 0.1 秒作为 strace 的超时时间:

#! /usr/bin/perl -w
use integer;
use strict;
$^F = 0x7fffffff;  # Disable close-on-exec on pipe().
my $input_filename = shift;
my $if;
die if !open($if, '<', $input_filename);
my($r,$w);
die if !pipe($r,$w);
my($sr,$sw);
die if !pipe($sr,$sw);
my $pid=fork();
die if !defined $pid;
if (!$pid) {
  close($if);
  close($w);
  close($sr);
  if (fileno($r)) { die if !open STDIN, '<&', $r; close($r); }
  die if !exec 'strace', '-o', '/proc/self/fd/' . fileno($sw), '-s', '4',
      '-e', 'trace=read', '--', @ARGV;
}
close($r);
close($sw);
{ my $old = select($w); $| = 1; select($old); }
$| = 1;
my($s,$got);
L: for (;;) {
  die if !defined($got = sysread($sr, $s, 4096));
  last if !$got;
  if ($s =~ /^read\(0, \Z(?!\n)/m) {
    { my $rin = '';
      vec($rin, fileno($sr), 1) = 1;
      $got = select($rin, undef, undef, .1);
      next L if $got;
    }
    $s = <$if>;
    if (!length($s)) {
      close($w);
      close($if);
    } else {
      print $s;
      print $w $s;
    }
  }
}
die if $pid != waitpid($pid, 0);
exit $?;

使用它:

$ chmod +x rtrace.pl
$ ./rtrace.pl INPUT java Test >OUTPUT

您可能希望将 -ff 添加到 Java 的 strace 参数列表中。

请注意,如果行长,可能会出现死锁问题。这个问题可以通过在sysread之后添加输入缓冲来解决。

【讨论】:

  • 我添加了 -ff 以便它可以观察分叉,但它说:$ ./rtrace.pl INPUT java Test &gt;OUTPUT strace: Can't fopen '/proc/self/fd/7.2134': No such file or directory
  • 抱歉,我没有更多的工程周期可以为此做出贡献。祝你好运!
  • 也许7.2134 问题有一个简单的答案:尝试strace -f 而不是strace -ff
【解决方案3】:

尚未编译/测试(尚未):

class EchoInputStream extends InputStream {

    private InputStream in;
    private OutputStream echoOut;

    public void EchoInputStream(InputStream in, OutputStream echoOut) {
        this.in = in;
        this.echoOut = out;
    }

    // Reads the next byte of data from in and echos to echoOut
    public int read() {
        int r = in.read();
        echoOut.write(r);
        return r;
    }

    // Reads a bytes from in, stores them into the buffer array b and echos to echoOut.
    int read(byte[] b) {
        in.read(b);
        echoOut.write(b);
    }

    // Reads up to len bytes of data from in into array b and echos them to echoOut.
    int read(byte[] b, int off, int len) {
        in.read(b, off, len);
        echoOut.write(b, off, len);
    }

  void reset() {
    in.reset();
  }

  // for other abstract methods, simply redirect to in
}

使用方法:

Scaner scan = new Scanner(new EchoInputStream(System.in, System.out));
System.out.print("Enter the value: ");
int value = scan.nextInt();
System.out.println("You entered: " + value); 

【讨论】:

    【解决方案4】:

    这是我的答案:

    1. 将此脚本另存为,例如wrap.sh

      #!/bin/bash
      while read -s input_line
      do
          raw_out=$(echo $input_line | $1) 
          header=${raw_out%%:*}:
          echo -e ${raw_out/$header/$header$input_line\\n}
      done < ${2:-/proc/${$}/fd/0} 
      
    2. chmod +x wrap.sh

    3. 像这样使用它:./wrap.sh 'java yourprog' inputfile.txt 输入文件有任意数量的数字,每行一个。

    4. 记住'java yourprog'周围的强制引号

    5. 如果你想以交互方式使用它,你仍然可以(只需省略输入文件):

      $ ./wrap.sh 'java yourprog'
      Enter number:12345
      You entered: 12345
      
    6. 您也可以像这样重定向:./wrap.sh 'java yourprog' &gt; outfile.txt,但输入 whoud 是不可见的,只存在于 outfile.txt 中。

    最后的话:

    1. 要求:您的程序应该提出类似 "Enter number:""Enter value:" 之类的问题(就像在您的代码中一样)包含一个冒号!。

    2. 用我的玩具 C prog 对其进行了测试 - 无需启动 java,因此它看起来更漂亮:)

    3. 这当然可以进行更多操作,但它提供了您要求的基本功能。

    【讨论】:

    • 这是一个不错的 hack,但它依赖于冒号的存在
    • 嗯,它可以依赖一些不同的分隔符。您没有指定它不能指定的要求。
    【解决方案5】:

    我认为 bash/zsh 中的命名管道、协同进程或进程替换可以解决您的问题。这是 bash 中使用进程替换的一个 hacky 解决方法。基本上,您将一次循环输入一行,将其重定向一次到您的 EXE,然后再一次重定向到您的输出。睡眠确保一切都得到正确订购。

     exec 6> >(./TEST.exe)
     sleep 1
     while read x; do
          echo $x
          sleep 1                                                                                                     
          echo $x >&6
          sleep 1
     done < INPUT.txt
    

    【讨论】:

      猜你喜欢
      • 2013-04-03
      • 2014-08-22
      • 2019-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-08
      • 1970-01-01
      相关资源
      最近更新 更多