Question

Let's have a simple Java application where I have the following code to scan for user input:

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

When I manually run it, as expected I see what I type, a sample run would be:

Enter the value: 3
You entered: 3

However, when I have a file with content

3

And I want to redirect the programs stdout to a file by this:

java Test < INPUT > OUTPUT

All I get is in cat OUTPUT:

Enter the value: You entered: 3

As the values inputted are not shown on the stdout, which is correct, they are not on stdout, I know, I see them because I manually type them to my terminal. But how can I make them also seen when I redirect the stdout like that?

Edit: I can see that code is making a read(0, syscall.

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

Is there a better way to intercept it?

Edit 2: I mean, the OUTPUT should be the following when I run it with java < INPUT > OUTPUT

Enter the value: 3
You entered: 3
Was it helpful?

Solution

You cannot modify the supplied code, but you can modify the way you launch it.

What do I offer is a hack that assumes change in System.in. What did I do:

1) I made a test program with single main method with code you post. I launched it:

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

2) I wrote a java programm that does

  1. redirect input stream (System.in)
  2. launch test.Test.main

code:

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) I launched old code with a new way:

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

Supplied code wasn't edited; it was just launched by tricky way. All the parameters and jvm options were saved. If you have several entry points, you may use reflection instead of simple call to test.Test.main(args);

Bad point: if someone will edit System.in in supplied code, the hack will be broken. But it seems to be safe until you work with standard input.

OTHER TIPS

Write another program, let's call it driver, call new Process() in the driver, execute this program in the process, connect the stdin to and stdout to the driver. As soon as the process writes Enter new value:, make the driver send the value from the input file etc. Make the driver print both its input and output.

Another option is that the driver runs the process within strace (Linux-specific), monitoring read(0, ...) calls.

Here is a solution in Perl (rtrace.pl) which uses 0.1 second as a timeout with 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 $?;

To use it:

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

You may want to add -ff to the strace argument list for Java.

Please note that there can be deadlock issues if lines are long. This issues can be resolved by adding input buffering after sysread.

Not compiled/tested (yet):

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
}

To use:

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

Here's my answer:

  1. Save this script as, say 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. use it like so: ./wrap.sh 'java yourprog' inputfile.txt with input files having as many numbers as you want, one per line.

  4. Remember the mandatory quotes around 'java yourprog'

  5. If you want to use it interactively, you still can (just ommit the input file):

    $ ./wrap.sh 'java yourprog'
    Enter number:12345
    You entered: 12345
    
  6. You can also redirect like this: ./wrap.sh 'java yourprog' > outfile.txt, but input whoud be invisible, only present in the outfile.txt.

Final words:

  1. Requirement: your program is expected to ask a question like "Enter number:" or "Enter value:" (just like in your code) that contains a colon!.

  2. tested it with my toy C prog - there's no need to launch java so it looks prettier:)

  3. This can be of course massaged much more, but it provides the basic functionality you asked for.

I think named pipes, coprocesses, or process substitution in bash/zsh could solve your issue. Here is a hacky workaround in bash using process substitution. Basically you would loop over your input one line at a time, redirecting it once to your EXE, and once again to your output. The sleeps make sure everything gets ordered correctly.

 exec 6> >(./TEST.exe)
 sleep 1
 while read x; do
      echo $x
      sleep 1                                                                                                     
      echo $x >&6
      sleep 1
 done < INPUT.txt
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top