How can I check if a Java program's input/output streams are connected to a terminal?
Asked Answered
T

6

53

I would like a Java program to have different default settings (verbosity, possibly colored output where supported) depending on its use. In C, there is an isatty() function which will return 1 if a file descriptor is connected to a terminal, and 0 otherwise. Is there an equivalent for this in Java? I haven't seen anything in the JavaDoc for InputStream or PrintStream.

Truckage answered 10/9, 2009 at 7:12 Comment(1)
I believe there's no such equivalent in Java. For the rest of the settings you can try this curses implementation in Java: javacurses.sourceforge.netLudvig
A
52

System.console() vs isatty()

System.console(), as already mentioned by @Bombe, works for simple use cases of checking console-connectedness. The problem with System.console() however, is that it doesn't let you determine whether it's STDIN or STDOUT (or both or neither) that is connected to a console.

The difference between Java's System.console() and C's isatty() can be illustrated in the following case-breakdown (where we pipe data to/from a hypothetical Foo.class):

1) STDIN and STDOUT are tty

%> java Foo
System.console() => <Console instance>
isatty(STDIN_FILENO) => 1
isatty(STDOUT_FILENO) => 1

2) STDOUT is tty

%> echo foo | java Foo
System.console() => null
isatty(STDIN_FILENO) => 0
isatty(STDOUT_FILENO) => 1

3) STDIN is tty

%> java Foo | cat
System.console() => null
isatty(STDIN_FILENO) => 1
isatty(STDOUT_FILENO) => 0

4) Neither STDIN nor STDOUT are tty

%> echo foo | java Foo | cat
System.console() => null
isatty(STDIN_FILENO) => 0
isatty(STDOUT_FILENO) => 0

I can't tell you why Java doesn't support better tty-checking. I wonder if some of Java's target OS's don't support it.

Using JNI to call isatty()

It technically is possible to do this in Java (as stephen-c@ pointed out) with some fairly simple JNI, but it will make your application dependent on C-code that may not be portable to other systems. I can understand that some people may not want to go there.

A quick example of what the JNI would look like (glossing over a lot of details):

Java: tty/TtyUtils.java

public class TtyUtils {
    static {
        System.loadLibrary("ttyutils");
    }
    // FileDescriptor 0 for STDIN, 1 for STDOUT
    public native static boolean isTty(int fileDescriptor);
}

C: ttyutils.c (assumes matching ttyutils.h), compiled to libttyutils.so

#include <jni.h>
#include <unistd.h>

JNIEXPORT jboolean JNICALL Java_tty_TtyUtils_isTty
          (JNIEnv *env, jclass cls, jint fileDescriptor) {
    return isatty(fileDescriptor)? JNI_TRUE: JNI_FALSE;
}

Other languages:

If you have the option of using another language, most other languages I can think of support tty-checking. But, since you asked the question, you probably already know that. The first that come to mind for me (aside from C/C++) are Ruby, Python, Golang and Perl.

Apologist answered 2/5, 2014 at 1:34 Comment(3)
Here's the native code Console calls to decide if it will exist or not - both stdin and stdout must be TTYs in order for a Console instance to be available.Shrewd
Note that System.console() == null isn't correct in JDK 22 and newer, the replacement is System.console(). isTerminal(), see bugs.openjdk.org/browse/JDK-8309155Mercola
@LiamMiller-Cushon Thank you very much for the info ! Better than nothing ! Although, that still doesn't allow to answer which side is interactive.Ventilation
L
32

System.console() will return the console your application is connected to if it is connected, otherwise it returns null. (Note that it’s only available from JDK 6 on.)

Lily answered 10/9, 2009 at 7:26 Comment(4)
@Bombe: this doesn't address the core question ... which is how to tell if an EXISTING Stream is connected to a console.Tolyl
What is an existing stream? How can a stream be connected to a terminal but not exist? Or are you trying to detect when a process loses its controlling terminal?Lily
@Bombe: Ah I see what the Console object does. But I still claim that this doesn't do what isatty does. Specifically, it does not tell you if a given stream is connected to the console.Tolyl
System.console() will be null if either stdin or stdout are redirected, but it doesn't tell you which one it is. This is relevant because sometimes you need to know which stream (if any) is connected to the console. For example, if stdout is redirected to a log file you might want to strip out ANSI escape codes but the same is not true if stdin is redirected.Speck
T
9

The short answer is that there is no direct equivalent of 'isatty' in standard Java. There's been a RFE for something like this in the Java Bug Database since 1997, but it only has had1 one measly vote.

In theory, you might be able to implement 'isatty' using JNI magic. But that introduces all sorts of potential problems. I wouldn't even contemplate doing this myself ...


1 - Voting for Java bugs to be fixed went away around the time that Oracle took over Sun.

Tolyl answered 10/9, 2009 at 7:41 Comment(2)
Thanks for the link to the RFE.Truckage
Broken Link, moved to bugs.java.com/bugdatabase/view_bug.do?bug_id=4099017Scudo
W
7

You could use jnr-posix library to call native posix methods from Java:

import jnr.posix.POSIX;
import jnr.posix.POSIXFactory;
import java.io.FileDescriptor;

POSIX posix = POSIXFactory.getPOSIX();

posix.isatty(FileDescriptor.out);
Wedded answered 3/4, 2015 at 3:7 Comment(0)
L
6

If you don't want to compile C source yourself, you can use the Jansi library. It's a lot smaller than jnr-posix

<dependency>
  <groupId>org.fusesource.jansi</groupId>
  <artifactId>jansi</artifactId>
  <version>1.17.1</version>
</dependency>

...

import static org.fusesource.jansi.internal.CLibrary.isatty;

...

System.out.println( isatty(STDIN_FILENO) );
Langland answered 26/9, 2018 at 21:47 Comment(0)
V
1

There is another way. I discovered this by chance as I needed to use /dev/tty. I noticed a FileSystemException is raised, when the Java program tries to create an InputStream from the tty device file if the program is not part of a TTY, like Gradle daemons. However if any of the stdin, stdout, or stderr is connected to a terminal, this code won't raise an exception with the message:

  • on macOS (Device not configured)
  • on Linux No such device or address

Unfortunately checking if /dev/tty exists and is readable will be true. This FSE only happens when actually trying to read from the file, without reading it.

// straw man check to identify if this is running in a terminal
// System.console() requires that both stdin and stdout are connected to a terminal
// which is not always the case (eg with pipes).
// However, it happens that trying to read from /dev/tty works
// when the application is connected to a terminal, and fails when not
// with the message
//     on macOS '(Device not configured)'
//     on Linux 'No such device or address'
//
// Unfortunately Files::notExists or Files::isReadable don't fail.
//noinspection EmptyTryBlock
try (var ignored = Files.newInputStream(Path.of("/dev/tty"))) {
  return "in a tty"
} catch (FileSystemException fileSystemException) {
  return "not in a tty";
}

While this approach is ugly, it avoids the use of third party libraries. This doesn't answer the question which of the standard stream is connected to a terminal though, for that it might me better to rely on a terminal library, like Jansi or JLine 3.

Ventilation answered 8/6, 2021 at 14:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.