Jak odczytać pojedynczy znak z konsoli w Javie (jak go wpisuje użytkownik)?

110

Czy istnieje łatwy sposób na odczytanie pojedynczego znaku z konsoli, gdy użytkownik wpisuje go w Javie? Czy to możliwe? Próbowałem z tymi metodami, ale wszystkie czekają, aż użytkownik naciśnie klawisz Enter :

char tmp = (char) System.in.read();
char tmp = (char) new InputStreamReader(System.in).read ();
char tmp = (char) System.console().reader().read();           // Java 6

Zaczynam myśleć, że System.in nie jest świadomy danych wejściowych użytkownika, dopóki nie zostanie naciśnięty klawisz Enter .

Wiktor Hugo
źródło

Odpowiedzi:

57

To, co chcesz zrobić, to przestawić konsolę w tryb „surowy” (pominięcie edycji linii i brak konieczności wprowadzania klawisza Enter) w przeciwieństwie do trybu „gotowanego” (wymagana edycja linii z klawiszem enter). W systemach UNIX polecenie „stty” może zmienić tryby.

Teraz, w odniesieniu do Javy ... zobacz Nieblokujące wejście konsoli w Pythonie i Javie . Fragment:

Jeśli twój program musi być oparty na konsoli, musisz przełączyć terminal z trybu liniowego na tryb znakowy i pamiętać o przywróceniu go przed zamknięciem programu. Nie ma przenośnego sposobu na zrobienie tego w różnych systemach operacyjnych.

Jedną z sugestii jest użycie JNI. Ponownie, to nie jest zbyt przenośne. Kolejną sugestią na końcu wątku i wspólną z powyższym postem jest przyjrzenie się użyciu jCurses .

Chris W. Rea
źródło
4
JCurses też nie jest zbyt przenośny ... Z JCurses README: "JCurses składa się z dwóch części: części niezależnej od platformy i części zależnej od platformy, która składa się z natywnej biblioteki współdzielonej, udostępniającej prymitywne operacje wejścia i wyjścia dla pierwszej części . ”
Ryan Fernandes
6
@RyanFernandes brzmi dla mnie całkiem przenośnie - pojedyncze narzędzie, które można uruchomić na wielu systemach (przy użyciu różnych zależności)
Antoniossss
25

Musisz przełączyć konsolę do trybu surowego. Nie ma wbudowanego niezależnego od platformy sposobu, aby się tam dostać. jCurses może być jednak interesujące.

W systemie Unix może to zadziałać:

String[] cmd = {"/bin/sh", "-c", "stty raw </dev/tty"};
Runtime.getRuntime().exec(cmd).waitFor();

Na przykład, jeśli chcesz wziąć pod uwagę czas między naciśnięciami klawiszy, oto przykładowy kod, aby się tam dostać.

nes1983
źródło
Działało dobrze dla mnie pod Linuksem
MrSmith42
4
Pracował również na Macu. Prawdopodobnie chcesz wspomnieć, że stty cooked </dev/ttynależy to uruchomić, gdy program musi powrócić do trybu buforowanego, a na pewno przed zakończeniem programu.
Kelvin
15

Nie ma przenośnego sposobu odczytywania surowych znaków z konsoli Java.

Niektóre obejścia zależne od platformy zostały przedstawione powyżej. Ale aby być naprawdę przenośnym, musiałbyś porzucić tryb konsoli i użyć trybu okienkowego, np. AWT lub Swing.

rustyx
źródło
22
Nie bardzo rozumiem, dlaczego na przykład Mono (lub CLR) ma to, System.Console.ReadKeyco działa na wszystkich platformach. Java dystrybuuje również JVM i JRE dla każdej platformy z bibliotekami i implementacjami zależnymi od platformy, więc to nie jest wymówka.
Martin Macak
13

Napisałem klasę RawConsoleInput w Javie, która używa JNA do wywoływania funkcji systemu operacyjnego Windows i Unix / Linux.

  • W systemie Windows używa _kbhit()i _getwch()z msvcrt.dll.
  • W tcsetattr()systemie Unix używa do przełączania konsoli w tryb niekanoniczny, System.in.available()do sprawdzania, czy dane są dostępne i System.in.read()do odczytu bajtów z konsoli. A CharsetDecodersłuży do konwersji bajtów na znaki.

Obsługuje nieblokujące wejście i miksowanie trybu surowego i normalnego wejścia w trybie liniowym.

Christian d'Heureuse
źródło
Jak mocno zostało to przetestowane / przetestowane pod kątem stresu?
Załóż pozew Moniki
2
@QPaysTaxes Testowanie warunków skrajnych jest trudne dla danych wejściowych konsoli. Myślę, że w tym przypadku ważniejsze byłoby przetestowanie go w różnych środowiskach (różne wersje Windows / Linux, 64/32 bit, Linux przez SSH, Telnet, port szeregowy lub konsolę desktopową itp.). Jak dotąd używam go tylko w moich prywatnych narzędziach testowych. Ale kod źródłowy jest stosunkowo mały w porównaniu do innych rozwiązań (takich jak JLine2, który wykorzystuje Jansi). Więc niewiele może się nie udać. Napisałem to, ponieważ JLine2 nie obsługuje wprowadzania pojedynczych znaków bez blokowania.
Christian d'Heureuse
To właśnie miałem na myśli mówiąc o testach wytrzymałościowych - to prawdopodobnie niewłaściwe słowo; mój błąd. W każdym razie fajnie! Ukradłem ^ H ^ H ^ H ^ H ^ H ^ Użyłem go w moim projekcie szkolnym i pomogło to wielu.
Załóż pozew Moniki
Hej - ta klasa wygląda świetnie. Jednak: nie mogę zmusić go do działania .. jak mam go używać? Zetknąłem się z blokowaniem System.in do momentu naciśnięcia CTRL + D (na Linuksie), a teraz czytam o trybach konsoli i polubieniach. Myślę, że szukam RawConsoleInput - ale jak go używać?
Igor
@Igor Po prostu wywołaj RawConsoleInput.read (boolean), aby odczytać znak klawiatury. Jest to udokumentowane w kodzie źródłowym (RawConsoleInput.java).
Christian d'Heureuse
8

Użyj jline3 :

Przykład:

Terminal terminal = TerminalBuilder.builder()
    .jna(true)
    .system(true)
    .build();

// raw mode means we get keypresses rather than line buffered input
terminal.enterRawMode();
reader = terminal .reader();
...
int read = reader.read();
....
reader.close();
terminal.close();
Strąk
źródło
Odkryłem, że rozwiązania oparte na RawConsoleInput nie działają na MacOS High Sierra; jednak działa to doskonale.
RawToast
jline ma praktycznie wszystko, czego potrzebujesz do stworzenia interaktywnego systemu konsoli / terminala. Działa świetnie w Linuksie. Bardziej kompletny przykład można znaleźć pod adresem: github.com/jline/jline3/blob/master/builtins/src/test/java/org/… . Ma autouzupełnianie, historię, maskę hasła itp.
lepe