1

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
Mustafa
  • 10,013
  • 10
  • 70
  • 116
  • 1
    Detect whether the input is a TTY (terminal), and if it's not, print the input data manually. Maybe detecting in Java is not so easy, see http://stackoverflow.com/questions/1403772/how-can-i-check-if-a-java-programs-input-output-streams-are-connected-to-a-term . (In most other languages this feature is built in.) – pts Nov 18 '13 at 22:04
  • I don't want to make modifications to the supplied code, they are coming from outside. – Mustafa Nov 18 '13 at 22:05
  • If you don't want to change the supplied code, what other options are OK for you? – pts Nov 18 '13 at 22:07
  • Anything but changing the supplied code. I can write another program, script to run the given code, and handle the input/output redirection differently. Maybe detect when the `java Test` is wanting input, and at that time write the content of INPUT to both OUTPUT and STDIN to file, but I am wondering if there is another, easier way, I feel like I am inventing the wheel. – Mustafa Nov 18 '13 at 22:10
  • Detecting when the `java Test` is wanting input is impossible. You can look at what it prints, and from that you can deduce when it most probably wants input. – pts Nov 18 '13 at 22:11
  • Most probably there is no easier way than writing the driver. Let's see if someone proposes a way. You may also try the program named `expect`, but I don't know it well enough to say if it can print what you want. – pts Nov 18 '13 at 22:12
  • It is not impossible, you can monitor syscalls with strace, I edited my answer with that command. – Mustafa Nov 18 '13 at 22:26
  • The strace idea is great, but it doesn't make the solution much simpler. I've extended my answer. – pts Nov 19 '13 at 09:04
  • I've just tried it: even `strace` can't say that the program is waiting for input. `strace` prints `read(0, ` if the program is waiting for input, but also when input is available, and the program is reading it. To distinguish, one should measure the time how long `strace` is stuck at `read(0, `. If it's long enough, then it's most probably waiting. These timeouts would make the method very flaky. – pts Nov 19 '13 at 10:10
  • I've modified my comment and added a `strace`-based script. – pts Nov 19 '13 at 11:01
  • @Mustafa: care to comment on my solution? – racic Nov 26 '13 at 17:19

5 Answers5

2

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.

pts
  • 80,836
  • 20
  • 110
  • 183
  • I added `-ff` so it can watch the forks, but it says: `$ ./rtrace.pl INPUT java Test >OUTPUT strace: Can't fopen '/proc/self/fd/7.2134': No such file or directory ` – Mustafa Nov 19 '13 at 19:32
  • Sorry, I don't have any more engineering cycles to contribute to this. Good luck! – pts Nov 19 '13 at 19:46
  • Maybe there is an easy answer for the `7.2134` problem: Try `strace -f` instead of `strace -ff`. – pts Nov 19 '13 at 21:44
2

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.

Sergey Fedorov
  • 2,169
  • 1
  • 15
  • 26
  • Although it is specific to java, it does not modify the given code and it gave me an idea, I am willing to implement this generic in generic form and post soon, thanks. – Mustafa Nov 26 '13 at 20:23
1

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); 
Glen Best
  • 22,769
  • 3
  • 58
  • 74
1

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.

racic
  • 1,235
  • 11
  • 20
0

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
Steve Goranson
  • 329
  • 2
  • 8