My program has a main while loop as the main logic, with an inner while loop for running the logic of the "command function". When inside the inner while loop, I want EOF (Ctrl + D) to exit from the inner while loop only and continue with the outer loop (to listen to more commands). However, its exiting from both the inner "command" loop and outer main while loop.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class Main {
public static void main(String[] args) {
String input, line;
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
while (true) {
input = reader.readLine();
if (input == null)
break;
if (input.compareTo("echo") == 0) {
while (true) {
line = reader.readLine();
// Ctrl+D (EOF) will exit loop
if (line == null)
break;
else System.out.println("echo: " + line);
}
System.out.println("Stop echo-ing");
}
else System.out.println("Cmd not recognized");
// No EOF given, should not exit this loop
}
System.out.println("Exit main loop");
} catch (IOException e) {
System.err.println(e.getMessage());
}
}
}
To replicate problem:
- Copy and paste code, and run it
- Type
echoto enter into inner while loop - Press Ctrl + D to provide EOF to standard input
The following is printed:
^D
Stop echo-ing
Exit main loop
"Exit main loop" is printed unexpectedly.
How can I exit only the inner while loop when Ctrl + D is pressed?
What you want is not possible. CTRL+D doesn't 'send you a signal', it terminates standard in. It 'closes'
System.in. The reason that your inner loop exits is because.readLine()returnsnullto signal 'the input source has closed'. Not because it returnsnullto signal: "User pressed CTRL+D". A thing that has closed is closed. Permanently. You can't un-close it.Find another way to signal 'I wish to exit the inner loop'. For example, by having the user type
DONEorEXITor whatnot.If that's not acceptable, read on for alternative solutions.
Signals
If you insist on using signal-esque features, java has not elevated the idea of 'OSes have signals they send to processes' to the "this is universal" level. Java is platform agnostic and has chosen the route of mostly not exposing any functionality that the underlying OS offers unless all OSes offer it in a way that java can unify.
But it is there - in a proprietary package that will get you warnings that it'll be removed soon. Of course. it's been emitting that warning for well over a decade; it still works in Java21:
This will intercept CTRL+C (which sends the interrupt (
INT) signal, and which is normally dealt with by a JDK by running all shutdown hooks and then shutting down the JVM).It's very complicated: That code is run in a separate thread, so it needs to send a message to your main thread. It also needs to interrupt the main thread as it is 'waiting' for input which is OS-dependent (if it is not specced to
throws InterruptedException, whether you can interrupt it is up to the implementation). It's a ton of really tricky work and all you get is that you can press CTRL+C (not CTRL+D!) to exit the inner loop, along with baggage that this will not work in future java versions.I Strongly recommend you don't do this.
Cooked mode
A process's Standard in (in java, this is exposed as
System.in) can be anything; if you runjava myapp <somefile.txt, then standard in is the contents of that file. But, by default, it's the keyboard. Java starts its terminal interactions in this case in so-called 'cooked mode'. You get no input at all until the user presses enter, but the user gets to edit their input, for example.You can move yourself to raw mode ('uncooked' mode). In raw mode, you get every keypress as they press it, and you can send terminal commands out that e.g. move the cursor, change the colour (or even background color) of the next thing that will be printed, and so forth.
In raw mode, CTRL+D and usually CTRL+C are simply sent to your app as if they were any other symbol - generally, with unicode value z where z is the letter in the alphabet (CTRL+A sends 1, CTRL+Z sends 26). As in,
in.read()would return 1. Not '1' the character, 1 the number.You probably don't want
readLine()then, this wouldn't help you detect CTRL+D. You want justread(), and after inspecting what you read, either add it to aStringBuilder(if they typed a normal letter), or process the input (if they typed enter, that'd bein.read() == '\n'), or do whatever you want to do in response to CTRL+D if it's 4. Such asbreakthe inner loop.Cooked mode brings a lot of niceties and you'd have to handwrite them all. Also, moving to raw mode is OS dependent and java has no baked in ability to do it.
It's very complicated as well and I strongly recommend you don't do this, either. But, read all about it if you really want to burn yourself on this.
No, no! I want something simple
Don't shoot the messenger, but there is no simple answer. It's one of two very complicated solutions, or, forget about using CTRL+D as non-terminal signalling mechanism.