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

如何检查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数据库中

我希望Java程序有不同的默认设置(详细程度,可能支持彩色输出),这取决于它的使用。在C语言中,有一个isatty()函数,如果文件描述符连接到终端,它将返回1,否则返回0。在Java中是否有类似的版本?我在JavaDoc中没有看到InputStream或PrintStream的任何内容。

将返回应用程序连接到的控制台(如果已连接),否则返回
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";
}