简体   繁体   中英

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

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.

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 .

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.)

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 had 1 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.

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);

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) );

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM