简体   繁体   English

如何检查 Java 程序的输入/输出流是否已连接到终端?

[英]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.我希望 Java 程序根据其用途具有不同的默认设置(详细程度,可能支持彩色输出)。 In C, there is an isatty() function which will return 1 if a file descriptor is connected to a terminal, and 0 otherwise.在 C 中,有一个 isatty() 函数,如果文件描述符连接到终端,它将返回 1,否则返回 0。 Is there an equivalent for this in Java?在 Java 中是否有这样的等价物? I haven't seen anything in the JavaDoc for InputStream or PrintStream.我在 JavaDoc 中没有看到 InputStream 或 PrintStream 的任何内容。

System.console() vs isatty() System.console() 与 isatty()

System.console(), as already mentioned by @Bombe, works for simple use cases of checking console-connectedness. System.console(),正如@Bombe 已经提到的,适用于检查控制台连接的简单用例。 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.然而,System.console() 的问题在于它不能让您确定连接到控制台的是 STDIN 还是 STDOUT(或两者都不是)。

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): Java 的System.console()和 C 的isatty()之间的区别可以在以下案例分解中说明(我们将数据传入/传出假设的 Foo.class):

1) STDIN and STDOUT are tty 1) STDIN 和 STDOUT 是 tty

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

2) STDOUT is tty 2) STDOUT 是 tty

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

3) STDIN is tty 3) STDIN 是 tty

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

4) Neither STDIN nor STDOUT are tty 4) STDIN 和 STDOUT 都不是 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.我无法告诉您为什么 Java 不支持更好的 tty 检查。 I wonder if some of Java's target OS's don't support it.我想知道某些 Java 的目标操作系统是否不支持它。

Using JNI to call isatty()使用 JNI 调用 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.从技术上讲可以使用一些相当简单的 JNI在 Java 中执行此操作(如 stephen-c@ 指出的那样),但它会使您的应用程序依赖于可能无法移植到其他系统的 C 代码。 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): JNI 外观的快速示例(掩盖了很多细节):

Java: tty/TtyUtils.java 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 C: ttyutils.c (假设匹配ttyutils.h ),编译为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.如果您可以选择使用另一种语言,我能想到的大多数其他语言都支持 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 .我首先想到的(除了 C/C++)是RubyPythonGolangPerl

System.console() will return the console your application is connected to if it is connected, otherwise it returns null . System.console()将返回您的应用程序连接到的控制台,否则返回null (Note that it's only available from JDK 6 on.) (请注意,它仅从 JDK 6 开始可用。)

The short answer is that there is no direct equivalent of 'isatty' in standard Java.简短的回答是,在标准 Java 中没有直接等价于 'isatty' 的东西。 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.还有的是一个RFE用于在Java错误数据库这样的事情,因为1997年,但它只是 1一个可怜的票。

In theory, you might be able to implement 'isatty' using JNI magic.理论上,您可以使用 JNI 魔法实现“isatty”。 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. 1 - 在 Oracle 接管 Sun 的时候,对修复 Java 错误的投票停止了。

You could use jnr-posix library to call native posix methods from Java:您可以使用jnr-posix库从 Java 调用本机 posix 方法:

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.如果不想自己编译 C 源代码,可以使用 Jansi 库。 It's a lot smaller than jnr-posix它比 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 .我偶然发现了这一点,因为我需要使用/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.我注意到一个FileSystemException被引发,当 Java 程序试图从 tty 设备文件创建一个InputStream ,如果程序不是 TTY 的一部分,比如 Gradle 守护进程。 However if any of the stdin, stdout, or stderr is connected to a terminal, this code won't raise an exception with the message:但是,如果 stdin、stdout 或 stderr 中的任何一个连接到终端,则此代码不会引发带有消息的异常:

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

Unfortunately checking if /dev/tty exists and is readable will be true.不幸的是,检查/dev/tty存在并且可读将是真的。 This FSE only happens when actually trying to read from the file, without reading it.此 FSE 仅在实际尝试从文件中读取时发生,而不是读取它。

// 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.这并没有回答哪个标准流连接到终端的问题,因为我最好依赖终端库,如 Jansi 或 JLine 3。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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