簡體   English   中英

如何檢查 Java 程序的輸入/輸出流是否已連接到終端?

[英]How can I check if a Java program's input/output streams are connected to a terminal?

我希望 Java 程序根據其用途具有不同的默認設置(詳細程度,可能支持彩色輸出)。 在 C 中,有一個 isatty() 函數,如果文件描述符連接到終端,它將返回 1,否則返回 0。 在 Java 中是否有這樣的等價物? 我在 JavaDoc 中沒有看到 InputStream 或 PrintStream 的任何內容。

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) 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 調用 isatty()

從技術上講可以使用一些相當簡單的 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++)是RubyPythonGolangPerl

System.console()將返回您的應用程序連接到的控制台,否則返回null (請注意,它僅從 JDK 6 開始可用。)

簡短的回答是,在標准 Java 中沒有直接等價於 'isatty' 的東西。 還有的是一個RFE用於在Java錯誤數據庫這樣的事情,因為1997年,但它只是 1一個可憐的票。

理論上,您可以使用 JNI 魔法實現“isatty”。 但這會帶來各種潛在的問題。 我什至不會考慮自己做這個......


1 - 在 Oracle 接管 Sun 的時候,對修復 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);

如果不想自己編譯 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 我注意到一個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。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM