如何检查Java程序';s输入/输出流是否连接到终端?
我希望Java程序有不同的默认设置(详细程度,可能支持彩色输出),这取决于它的使用。在C语言中,有一个isatty()函数,如果文件描述符连接到终端,它将返回1,否则返回0。在Java中是否有类似的版本?我在JavaDoc中没有看到InputStream或PrintStream的任何内容。将返回应用程序连接到的控制台(如果已连接),否则返回如何检查Java程序';s输入/输出流是否连接到终端?,java,console,terminal,Java,Console,Terminal,我希望Java程序有不同的默认设置(详细程度,可能支持彩色输出),这取决于它的使用。在C语言中,有一个isatty()函数,如果文件描述符连接到终端,它将返回1,否则返回0。在Java中是否有类似的版本?我在JavaDoc中没有看到InputStream或PrintStream的任何内容。将返回应用程序连接到的控制台(如果已连接),否则返回null。(请注意,它只能从JDK 6上获得。)简单的回答是,在标准Java中没有与“isatty”直接等价的东西。自1997年以来,Java Bug数据库中
null
。(请注意,它只能从JDK 6上获得。)简单的回答是,在标准Java中没有与“isatty”直接等价的东西。自1997年以来,Java Bug数据库中就出现了类似这样的问题,但它只获得了区区一票
理论上,您可能能够使用JNI magic实现“isatty”。但这带来了各种潜在的问题。我甚至不会考虑自己做这件事
1-在Oracle接管Sun前后,投票支持修复Java bug的声音消失了。System.console()vs isatty() 正如@Bombe已经提到的,System.console()适用于检查控制台连接性的简单用例。然而,System.console()的问题在于,它不允许您确定连接到控制台的是STDIN还是STDOUT(或者两者都是,或者两者都不是) Java和C之间的差异可以在下面的案例分解中说明(在这里,我们通过管道将数据传输到/从一个假设的Foo.class): 1) STDIN和STDOUT是tty
%> java Foo
System.console() => <Console instance>
isatty(STDIN_FILENO) => 1
isatty(STDOUT_FILENO) => 1
%> echo foo | java Foo | cat
System.console() => null
isatty(STDIN_FILENO) => 0
isatty(STDOUT_FILENO) => 0
3) STDIN是tty
%> java Foo | cat
System.console() => null
isatty(STDIN_FILENO) => 1
isatty(STDOUT_FILENO) => 0
4) STDIN和STDOUT都不是tty
%> java Foo
System.console() => <Console instance>
isatty(STDIN_FILENO) => 1
isatty(STDOUT_FILENO) => 1
%> echo foo | java Foo | cat
System.console() => null
isatty(STDIN_FILENO) => 0
isatty(STDOUT_FILENO) => 0
我无法告诉您为什么Java不支持更好的tty检查。我想知道Java的一些目标操作系统是否不支持它
使用JNI调用isatty()
从技术上讲,用Java(正如stephen-c@所指出的那样)实现这一点是可能的,但它会使您的应用程序依赖于c代码,而c代码可能无法移植到其他系统。我可以理解,有些人可能不想去那里
JNI外观的一个简单示例(忽略了很多细节):
Java:tty/TtyUtils.Java
C:ttyutils.C(假定匹配ttyutils.h),编译为libttyutils.so
#包括
#包括
JNIEXPORT jboolean JNICALL Java_tty_TtyUtils_isty
(JNIEnv*env、jclass cls、jint文件描述符){
返回isatty(fileDescriptor)?JNI\u TRUE:JNI\u FALSE;
}
其他语文:
如果您可以选择使用另一种语言,我能想到的大多数其他语言都支持tty检查。但是,既然你问了这个问题,你可能已经知道了。我首先想到的(除了C/C++)是和。您可以使用库从Java调用本机posix方法:
import 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>
还有另一种方法。当我需要使用
/dev/tty
时,我偶然发现了这一点。我注意到当Java程序试图从tty设备文件创建InputStream
时,如果程序不是tty的一部分(如Gradle守护进程),会引发FileSystemException
。但是,如果任何stdin、stdout或stderr连接到终端,此代码不会引发异常,并显示以下消息:
- 在macOS上
(设备未配置)
- 在Linux上,没有这样的设备或地址
/dev/tty
是否存在且可读性将为真。此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)可能会更好。我相信Java中没有这样的等价物。对于其余的设置,您可以在Java中尝试这个curses实现:@Bombe:这并没有解决核心问题。。。这是如何判断现有流是否连接到控制台的。什么是现有流?如何将流连接到终端但不存在?或者你是在试图检测一个进程何时失去了它的控制终端?@Bombe:啊,我知道Console对象的作用了。但我仍然声称这并不像isatty做的那样。具体地说,它不会告诉您给定的流是否连接到控制台。
System.console()
如果重定向了stdin
或stdout
,则将为空,但它不会告诉您它是哪一个流。这是相关的,因为有时您需要知道哪个流(如果有的话)连接到控制台。例如如果stdout
被重定向到一个日志文件,您可能想删除它,但是如果stdin
被重定向,情况就不一样了。感谢链接到RFE。下面是控制台
调用,以决定它是否存在-为了控制台
stdin和stdout
都必须是TTYs实例将可用。
import static org.fusesource.jansi.internal.CLibrary.isatty;
...
System.out.println( isatty(STDIN_FILENO) );
// 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";
}