1

I'm writing a Java program that reads a file from stdin and then prompts the user for interactive input. It should be executed like:

cat file.txt | java -jar myprog.jar
#
# ...Read file.txt then prompt user:
#
Some input please: 

The problem is that after reading file.txt, the stdin seems "used up" and I don't know how to restore it to get user's input. Here's an example of what I'm trying to do:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) throws IOException {

        BufferedReader br= new BufferedReader(new InputStreamReader(System.in));
        String line;
        while((line = br.readLine()) != null) {
            // Do something with line
            System.out.println(line);
        }
        br.close();

        Scanner scanner = new Scanner(System.in);
        String input= scanner.nextLine();

    }
}

When I executed it, I get:

echo -e "foo\nbar\nbaz" | java -jar biojava-tmp.jar 
foo
bar
baz
Exception in thread "main" java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1651)
    at biojava_tmp.Main.main(Main.java:21)

How can I resolve this?


PS: In my real program I don't use Scanner, instead I use jline as below but I figure the problem is the same.

ConsoleReader console= new ConsoleReader();
console.readLine()
dariober
  • 8,240
  • 3
  • 30
  • 47
  • 1
    Never close System.in or an object that wraps it until you are fully done using it. – Hovercraft Full Of Eels May 26 '19 at 18:02
  • @HovercraftFullOfEels - thanks but removing `br.close()` doesn't change the issue – dariober May 26 '19 at 18:09
  • 1
    It seems to me that when you start your program, the System.in isn't getting input from the console but from the file (since you're piping the text file into the Java program from the OS). I don't know how to re-direct it to the console. Why not instead pass the file path into the program on start-up, and then – Hovercraft Full Of Eels May 26 '19 at 19:49
  • Not sure what you mean by _pass the file path into the program on start-up, and then –_. In reality, the input may come from something like `sort file.txt | cut -f1-3 | ... | java -jar myprog.jar` – dariober May 26 '19 at 21:15
  • 1
    By your command line calls, you are telling the operating system to send the contents of the file to your Java program's standard input stream, and so this stream is no longer available to communicate with the OS's console. A way around this, to allow both is to pass the file name as a command line parameter, create a FileInputStream wrapped by a BufferedInputStream, and this will leave the System.in unmolested and still attached to the OS's system console. – Hovercraft Full Of Eels May 26 '19 at 22:38
  • Sorry for being thick but..." _pass the file name as a command line parameter_" doesn't this defeat the point of what I'm trying to do? That is, read a file from stdin first and then get the user's interactive input? – dariober May 27 '19 at 07:52
  • 1
    No expert at this, but I have to wonder if what you're trying to do is impossible to do. But the best way to find out for sure is to place a bounty on your question after a few days. – Hovercraft Full Of Eels May 27 '19 at 11:19

1 Answers1

1

It is not possible to do it the way you originally wanted to. Standard file descriptors are created by operating system as the process is being prepared to execute. When executed in a pipeline, downstream process' input file descriptor is bound to the upstream process' output file descriptor, hence only the first process in the pipeline has the input stream associated with the console.

It is however possible to redirect a non-standard file descriptor to a process from the file and execute the command as the only process in the pipeline like this:

someCommand 3<file.txt

In principle, it works just fine. someCommand could read fd3 (file descriptor 3) and then read from standard input (fd0) as well. The catch is, it doesn't just work with pure java.

Java does not support accessing non-standard file descriptors. So even if operating system binds the file you want to fd3, there's no way for your class to get to it without leaving cosy JVM context. It was discussed in a separate question Using a numbered file descriptor from Java and a user named user180100 has shared an interesting resource with some crude JNI solution: https://www.kfu.com/~nsayer/Java/jni-filedesc.html In essence, native method is called which constructs a FileDescriptor class instance and sets it's fd field to an integer value of file descriptor returned by fopen or fileno called from within the native context.

Krzysztof Jabłoński
  • 1,890
  • 1
  • 20
  • 29