我希望一个Java程序可以根据其用途设置不同的默认设置(详细程度,可能会有支持彩色输出)。在C语言中,有一个isatty()函数,如果文件描述符连接到终端则返回1,否则返回0。在Java中是否有类似的功能?在InputStream或PrintStream的JavaDoc中没有看到相关内容。
如@Bombe所述,System.console()适用于简单的检查连接到控制台的情况。然而,System.console()的问题在于它不让您确定是STDIN还是STDOUT(或两者都没有)与控制台连接。
Java的System.console()和C的isatty()之间的区别可以在以下情况下进行说明(我们将数据传输到/从一个假想的Foo.class):
1) STDIN和STDOUT均为tty
%> java Foo
System.console() => <Console instance>
isatty(STDIN_FILENO) => 1
isatty(STDOUT_FILENO) => 1
2)STDOUT 是 tty
%> echo foo | java Foo
System.console() => null
isatty(STDIN_FILENO) => 0
isatty(STDOUT_FILENO) => 1
3) STDIN 是 tty
%> java Foo | cat
System.console() => null
isatty(STDIN_FILENO) => 1
isatty(STDOUT_FILENO) => 0
4) STDIN和STDOUT都不是tty
%> echo foo | java Foo | cat
System.console() => null
isatty(STDIN_FILENO) => 0
isatty(STDOUT_FILENO) => 0
我无法告诉你为什么Java不支持更好的tty检查。我想知道是否有一些Java的目标操作系统不支持它。
技术上讲,通过一些相当简单的JNI方法,在Java中实现这一点是可能的(正如stephen-c@所指出的),但这将使您的应用程序依赖于C代码,该代码可能不可移植到其他系统。我可以理解有些人可能不想那样做。
下面是JNI的一个快速示例(省略了很多细节):
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(假定有匹配的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;
}
如果你可以选择使用另一种语言,我能想到的大多数其他语言都支持tty检查。但是,既然你已经问了这个问题,你可能已经知道了。除了C/C++之外,我首先想到的是Ruby、Python、Golang 和 Perl。
System.console() == null
是不正确的,替代方法是System.console().isTerminal()
,请参阅https://bugs.openjdk.org/browse/JDK-8309155。 - Liam Miller-CushonSystem.console() 方法将返回你的应用程序所连接的控制台,如果没有连接则返回 null
。(请注意,该方法只在 JDK 6 及以后版本中可用。)
stdin
或者 stdout
被重定向了,那么 System.console()
将会是空值,但它并不会告诉你哪一个被重定向了。这很重要,因为有时候你需要知道哪一个流(如果有)连接到了控制台。例如,如果 stdout
被重定向到了日志文件,你可能想要去掉 ANSI 转义码,但如果是 stdin
被重定向了,就不是这样。 - Giliimport jnr.posix.POSIX;
import jnr.posix.POSIXFactory;
import java.io.FileDescriptor;
POSIX posix = POSIXFactory.getPOSIX();
posix.isatty(FileDescriptor.out);
如果您不想自己编译C源代码,可以使用Jansi库。它比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) );
/dev/tty
。如果Java程序不是TTY的一部分(如Gradle守护进程),当程序尝试从tty设备文件创建InputStream
时,会引发FileSystemException
。但是,如果任何一个标准输入、标准输出或标准错误连接到终端,此代码将不会引发带有以下消息的异常:
(Device not configured)
No such device or address
不幸的是,检查/dev/tty
是否存在且可读将返回true。只有在实际尝试读取文件而没有读取它时,才会出现此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";
}
虽然这种方法不太美观,但它避免了使用第三方库。不过这并没有回答哪个标准流连接到终端的问题,对此最好依赖于终端库,比如Jansi或JLine 3。