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

Posted

技术标签:

【中文标题】如何检查 Java 程序的输入/输出流是不是连接到终端?【英文标题】:How can I check if a Java program's input/output streams are connected to a terminal?如何检查 Java 程序的输入/输出流是否连接到终端? 【发布时间】:2010-11-27 02:07:25 【问题描述】:

我希望 Java 程序根据其用途具有不同的默认设置(详细程度,可能支持彩色输出)。在 C 中,有一个 isatty() 函数,如果文件描述符连接到终端,则返回 1,否则返回 0。在 Java 中是否有等价物?我在 JavaDoc 中没有看到 InputStream 或 PrintStream 的任何内容。

【问题讨论】:

我相信 Java 中没有这样的等价物。对于其余的设置,您可以尝试在 Java 中使用这个 curses 实现:javacurses.sourceforge.net 【参考方案1】:

System.console() 与 isatty()

System.console(),正如@Bombe 已经提到的,适用于检查控制台连接的简单用例。然而,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) 标准输入是 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调用isatty()

技术上可以用一些fairly simple 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-checking。但是,既然你问了这个问题,你可能已经知道了。我首先想到的(除了 C/C++)是Ruby、Python、Golang 和 Perl。

【讨论】:

这里是 native code Console 调用来决定它是否存在 - stdinstdout 都必须是 TTY,以便 Console 实例可用。 【参考方案2】:

System.console() 将返回您的应用程序连接到的控制台(如果已连接),否则返回 null。 (请注意,它仅从 JDK 6 开始可用。)

【讨论】:

@Bombe:这并没有解决核心问题......这是如何判断现有流是否连接到控制台。 什么是现有流?流如何连接到终端但不存在?或者您是否试图检测进程何时失去其控制终端? @Bombe:啊,我看到了 Console 对象的作用。但我仍然声称这并没有像 isatty 那样做。具体来说,它不会告诉您给定的流是否连接到控制台。 如果 either stdinstdout 被重定向,System.console() 将为空,但它不会告诉您它是哪一个。这是相关的,因为有时您需要知道哪个流(如果有)连接到控制台。例如,如果 stdout 被重定向到一个日志文件,您可能希望删除 ANSI escape codes,但如果 stdin 被重定向,则情况并非如此。【参考方案3】:

简短的回答是在标准 Java 中没有直接等效的“isatty”。自 1997 年以来,Java Bug 数据库中就出现了类似问题的 RFE,但它只有 1 一票。

理论上,您也许可以使用 JNI 魔法来实现“isatty”。但这会带来各种潜在的问题。我什至不会考虑自己这样做......


1 - 在甲骨文收购 Sun 时,投票支持修复 Java 错误的投票就消失了。

【讨论】:

感谢 RFE 的链接。 链接失效,移至bugs.java.com/bugdatabase/view_bug.do?bug_id=4099017【参考方案4】:

您可以使用 jnr-posix 库从 Java 调用本机 posix 方法:

import jnr.posix.POSIX;
import jnr.posix.POSIXFactory;
import java.io.FileDescriptor;

POSIX posix = POSIXFactory.getPOSIX();

posix.isatty(FileDescriptor.out);

【讨论】:

【参考方案5】:

如果您不想自己编译 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) );

【讨论】:

【参考方案6】:

还有另一种方法。我偶然发现了这一点,因为我需要使用/dev/tty。我注意到一个FileSystemException 被引发,当Java 程序尝试从tty 设备文件创建一个InputStream 时,如果该程序不是TTY 的一部分,比如Gradle 守护进程。但是,如果任何 stdin、stdout 或 stderr 连接到终端,则此代码不会引发异常并显示以下消息:

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

不幸的是,检查/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-输入输出流

将输入流连接到输出流

Java:流式IO流和文件

我需要知道如何让 python 不断检查它是不是连接到互联网 [重复]

JAVA的IO编程:管道流

如何在android中检查网络是不是连接到wifi [重复]