写入stdout,也打印提供的输入,就像真正的终端一样

时间:2013-11-18 22:03:24

标签: java linux shell io-redirection

让我们有一个简单的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

我希望将程序stdout重定向到一个文件:

java Test < INPUT > OUTPUT

我得到的只是cat OUTPUT

Enter the value: You entered: 3

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

修改 我可以看到代码正在制作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 < INPUT > OUTPUT

运行时,OUTPUT应该是以下内容
Enter the value: 3
You entered: 3

5 个答案:

答案 0 :(得分:2)

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

另一个选择是驱动程序在strace(特定于Linux)中监视进程,监视read(0, ...)次调用。

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

#! /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之后添加输入缓冲来解决此问题。

答案 1 :(得分:2)

您无法修改提供的代码,但可以修改启动方式。

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

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
  3. 代码:

    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,那么黑客攻击将会被破坏。但在使用标准输入之前,它似乎是安全的。

答案 2 :(得分:1)

未编译/测试(尚未):

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); 

答案 3 :(得分:1)

这是我的答案:

  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. 的强制性报价
  6. 如果您想以交互方式使用它,您仍然可以(只是省略输入文件):

    $ ./wrap.sh 'java yourprog'
    Enter number:12345
    You entered: 12345
    
  7. 您也可以像这样重定向:./wrap.sh 'java yourprog' > outfile.txt,但输入视为不可见,仅存在于outfile.txt

  8. 最后的话:

    1. 要求:您的计划需要提出一个问题,例如“输入数字:”“输入值:”(就像在您的代码中一样)包含一个冒号!

    2. 用我的玩具C编程测试它 - 没有必要启动java所以它看起来更漂亮:)

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

答案 4 :(得分:0)

我认为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