确定是否已从交互式shell启动Java程序

Bas*_*ass 12 java windows bash shell cygwin

我以前以为

System.console() != null
Run Code Online (Sandbox Code Playgroud)

是一种可靠的方法来确定启动我的Java应用程序的shell是否是交互式的.这允许我使用ANSI转义序列中的互动模式,普通System.out/ System.err每当程序的输出被重定向到文件或管道输送到其他进程的标准输入,类似--color=auto的许多GNU工具模式.

System.console()但是,Windows中的行为是不同的.虽然方法返回非null当JVM从启动值cmd.exe(这是无用的我,cmd.exe不理解转义序列),返回值总是 null当我从任何终端模拟器,可在启动我的程序的Cygwin - xterm,minttycygwin(最后一个只是一个cmd.exe正在运行的bash子进程).

如何在Java中测试交互式shell,无需读取$-shell脚本并将命令行参数传递给我的Java程序?PS1从Java 测试环境变量不是一种选择,因为Java是从shell脚本启动的,因此父进程是非交互式shell,并且PS1未设置.

Rus*_*nov 2

在一次对话中,Cygwin 的维护者 (Corinna Vinschen) 解释说 Cygwin 伪 TTY 看起来像 Microsoft Visual C 运行时库 (MSVCRT) 的管道。isatty()她还建议围绕识别 Cygwin 伪 TTY 的函数实现一个包装器。

\n\n

这个想法是获取与给定文件描述符关联的管道的名称。该NtQueryInformationFile函数获取FILE_NAME_INFORMATION结构,其中FileName成员包含管道名称。如果管道名称与以下模式匹配,则该命令很可能在交互模式下运行:

\n\n
\\cygwin-%16llx-pty%d-{to,from}-master\n
Run Code Online (Sandbox Code Playgroud)\n\n

该对话相当古老,但管道名称的格式仍然相同:\n "\\\\\\\\.\\\\pipe\\\\cygwin-" + "%S-" ++ "pty%d-from-master",其中"\\\\\\\\.\\\\pipe\\\\"是命名管道的约定前缀(请参阅 参考资料CreateNamedPipe)。

\n\n

所以 Cygwin 部分已经被黑了。下一步是从 C 代码创建 Java 函数。

\n\n

例子

\n\n

下面创建带有通过 Java 本机接口 (JNI) 实现的方法的ttyjni.TestApp类。该代码在 GNU/Linux ( ) 和 Windows 7(64 位)上的 Cygwin 上istty()进行了测试。x86_64该代码可以轻松移植到 Windows ( cmd.exe),甚至可以按原样运行。

\n\n

所需组件

\n\n
    \n
  • x86_64-w64-mingw32-gcc带编译器的Cygwin
  • \n
  • 带有 JDK 的 Windows
  • \n
\n\n

布局

\n\n
\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 Makefile\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 TestApp.c\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 test.sh\n\xe2\x94\x9c\xe2\x94\x80\xe2\x94\x80 ttyjni\n\xe2\x94\x82\xc2\xa0\xc2\xa0 \xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 TestApp.java\n\xe2\x94\x94\xe2\x94\x80\xe2\x94\x80 ttyjni_TestApp.h\n
Run Code Online (Sandbox Code Playgroud)\n\n

生成文件

\n\n
# Input: $JAVA_HOME\n\nFINAL_TARGETS := TestApp.class\n\nifeq ($(OS),Windows_NT)\n  CC=x86_64-w64-mingw32-gcc\n  FINAL_TARGETS += testapp.dll\nelse\n  CC=gcc\n  FINAL_TARGETS += libtestapp.so\nendif\n\nall: $(FINAL_TARGETS)\n\nTestApp.class: ttyjni/TestApp.java\n  javac $<\n\ntestapp.dll: TestApp.c TestApp.class\n  $(CC) \\\n    -Wl,--add-stdcall-alias \\\n    -D__int64="long long" \\\n    -D_isatty=isatty -D_fileno=fileno \\\n    -I"$(JAVA_HOME)/include" \\\n    -I"$(JAVA_HOME)/include/win32" \\\n    -shared -o $@ $<\n\nlibtestapp.so: TestApp.c\n  $(CC) \\\n    -I"$(JAVA_HOME)/include" \\\n    -I"$(JAVA_HOME)/include/linux" \\\n    -fPIC \\\n    -o $@ -shared -Wl,-soname,testapp.so $<  \\\n    -z noexecstack\n\nclean:\n  rm -f *.o $(FINAL_TARGETS) ttyjni/*.class\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试应用程序.c

\n\n
#include <jni.h>\n#include <stdio.h>\n#include "ttyjni_TestApp.h"\n\n#if defined __CYGWIN__ || defined __MINGW32__ || defined __MINGW64__\n#include <io.h>\n#include <errno.h>\n#include <wchar.h>\n#include <windows.h>\n#include <winternl.h>\n#include <unistd.h>\n\n\n/* vvvvvvvvvv From http://cygwin.com/ml/cygwin/2012-11/txt00003.txt vvvvvvvv */\n\n#ifndef __MINGW64_VERSION_MAJOR\n/* MS winternl.h defines FILE_INFORMATION_CLASS, but with only a\n   different single member. */\nenum FILE_INFORMATION_CLASSX\n{\n  FileNameInformation = 9\n};\n\ntypedef struct _FILE_NAME_INFORMATION\n{\n  ULONG FileNameLength;\n  WCHAR FileName[1];\n} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;\n\nNTSTATUS (NTAPI *pNtQueryInformationFile) (HANDLE, PIO_STATUS_BLOCK, PVOID,\n    ULONG, FILE_INFORMATION_CLASSX);\n#else\nNTSTATUS (NTAPI *pNtQueryInformationFile) (HANDLE, PIO_STATUS_BLOCK, PVOID,\n    ULONG, FILE_INFORMATION_CLASS);\n#endif\n\njint\ntestapp_isatty(jint fd)\n{\n  HANDLE fh;\n  NTSTATUS status;\n  IO_STATUS_BLOCK io;\n  long buf[66]; /* NAME_MAX + 1 + sizeof ULONG */\n  PFILE_NAME_INFORMATION pfni = (PFILE_NAME_INFORMATION) buf;\n  PWCHAR cp;\n\n\n  /* First check using _isatty.\n\n     Note that this returns the wrong result for NUL, for instance!\n     Workaround is not to use _isatty at all, but rather GetFileType\n     plus object name checking. */\n  if (_isatty(fd))\n    return 1;\n\n  /* Now fetch the underlying HANDLE. */\n  fh = (HANDLE)_get_osfhandle(fd);\n  if (!fh || fh == INVALID_HANDLE_VALUE) {\n    errno = EBADF;\n    return 0;\n  }\n\n  /* Must be a pipe. */\n  if (GetFileType (fh) != FILE_TYPE_PIPE)\n    goto no_tty;\n\n  /* Calling the native NT function NtQueryInformationFile is required to\n     support pre-Vista systems.  If that\'s of no concern, Vista introduced\n     the GetFileInformationByHandleEx call with the FileNameInfo info class,\n     which can be used instead. */\n  if (!pNtQueryInformationFile) {\n    pNtQueryInformationFile = (NTSTATUS (NTAPI *)(HANDLE, PIO_STATUS_BLOCK,\n          PVOID, ULONG, FILE_INFORMATION_CLASS))\n      GetProcAddress(GetModuleHandle("ntdll.dll"), "NtQueryInformationFile");\n    if (!pNtQueryInformationFile)\n      goto no_tty;\n  }\n  if (!NT_SUCCESS (pNtQueryInformationFile (fh, &io, pfni, sizeof buf,\n          FileNameInformation)))\n    goto no_tty;\n\n  /* The filename is not guaranteed to be NUL-terminated. */\n  pfni->FileName[pfni->FileNameLength / sizeof (WCHAR)] = L\'\\0\';\n\n  /* Now check the name pattern.  The filename of a Cygwin pseudo tty pipe\n     looks like this:\n\n     \\cygwin-%16llx-pty%d-{to,from}-master\n\n     %16llx is the hash of the Cygwin installation, (to support multiple\n     parallel installations), %d id the pseudo tty number, "to" or "from"\n     differs the pipe direction. "from" is a stdin, "to" a stdout-like\n     pipe. */\n  cp = pfni->FileName;\n  if (!wcsncmp(cp, L"\\\\cygwin-", 8)\n      && !wcsncmp (cp + 24, L"-pty", 4))\n  {\n    cp = wcschr(cp + 28, \'-\');\n    if (!cp)\n      goto no_tty;\n    if (!wcscmp (cp, L"-from-master") || !wcscmp (cp, L"-to-master"))\n      return 1;\n  }\nno_tty:\n  errno = EINVAL;\n  return 0;\n}\n\n/* ^^^^^^^^^^ From http://cygwin.com/ml/cygwin/2012-11/txt00003.txt ^^^^^^^^ */\n\n#elif _WIN32\n#include <io.h>\n\nstatic jint\ntestapp_isatty(jint fd)\n{\n  return _isatty(fd);\n}\n#elif defined __linux__ || defined __sun || defined __FreeBSD__\n#include <unistd.h>\n\nstatic jint\ntestapp_isatty(jint fd)\n{\n  return isatty(fd);\n}\n#else\n#error Unsupported platform\n#endif /* __CYGWIN__ */\n\nJNIEXPORT jboolean JNICALL Java_ttyjni_TestApp_istty\n(JNIEnv *env, jobject obj)\n{\n  return testapp_isatty(fileno(stdin)) &&\n    testapp_isatty(fileno(stdout)) ?\n    JNI_TRUE : JNI_FALSE;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

ttyjni_TestApp.h

\n\n
/* DO NOT EDIT THIS FILE - it is machine generated */\n#include <jni.h>\n/* Header for class ttyjni_TestApp */\n\n#ifndef _Included_ttyjni_TestApp\n#define _Included_ttyjni_TestApp\n#ifdef __cplusplus\nextern "C" {\n#endif\n/*\n * Class:     ttyjni_TestApp\n * Method:    istty\n * Signature: ()Z\n */\nJNIEXPORT jboolean JNICALL Java_ttyjni_TestApp_istty\n  (JNIEnv *, jobject);\n\n#ifdef __cplusplus\n}\n#endif\n#endif\n
Run Code Online (Sandbox Code Playgroud)\n\n

ttyjni/TestApp.java

\n\n
package ttyjni;\n\nimport java.io.Console;\nimport java.lang.reflect.Method;\n\nclass TestApp {\n    static {\n        System.loadLibrary("testapp");\n    }\n    private native boolean istty();\n\n    private static final String ISTTY_METHOD = "istty";\n    private static final String INTERACTIVE = "interactive";\n    private static final String NON_INTERACTIVE = "non-interactive";\n\n    protected static boolean isInteractive() {\n        try {\n            Method method = Console.class.getDeclaredMethod(ISTTY_METHOD);\n            method.setAccessible(true);\n            return (Boolean) method.invoke(Console.class);\n        } catch (Exception e) {\n            System.out.println(e.toString());\n        }\n\n        return false;\n    }\n\n    public static void main(String[] args) {\n        // Testing JNI\n        TestApp t = new TestApp();\n        boolean b = t.istty();\n        System.out.format("%s(jni)\\n", b ?\n                "interactive" : "non-interactive");\n\n        // Testing pure Java\n        System.out.format("%s(console)\\n", System.console() != null ?\n                INTERACTIVE : NON_INTERACTIVE);\n        System.out.format("%s(java)\\n", isInteractive() ?\n                INTERACTIVE : NON_INTERACTIVE);\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

测试文件

\n\n
#!/bin/bash -\njava -Djava.library.path="$(dirname "$0")" ttyjni.TestApp\n
Run Code Online (Sandbox Code Playgroud)\n\n

编译

\n\n
make\n
Run Code Online (Sandbox Code Playgroud)\n\n

在 Linux 上测试

\n\n
$ ./test.sh\ninteractive(jni)\ninteractive(console)\ninteractive(java)\n\n$ ./test.sh > 1\nruslan@pavilion ~/tmp/java $ cat 1\nnon-interactive(jni)\nnon-interactive(console)\nnon-interactive(java)\n
Run Code Online (Sandbox Code Playgroud)\n\n

在 Cygwin 上测试

\n\n
$ ./test.sh\ninteractive(jni)\nnon-interactive(console)\nnon-interactive(java)\n\n$ ./test.sh > 1\n$ cat 1\nnon-interactive(jni)\nnon-interactive(console)\nnon-interactive(java)\n
Run Code Online (Sandbox Code Playgroud)\n