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
,mintty
或cygwin
(最后一个只是一个cmd.exe
正在运行的bash
子进程).
如何在Java中测试交互式shell,无需读取$-
shell脚本并将命令行参数传递给我的Java程序?PS1
从Java 测试环境变量不是一种选择,因为Java是从shell脚本启动的,因此父进程是非交互式shell,并且PS1
未设置.
在一次对话中,Cygwin 的维护者 (Corinna Vinschen) 解释说 Cygwin 伪 TTY 看起来像 Microsoft Visual C 运行时库 (MSVCRT) 的管道。isatty()
她还建议围绕识别 Cygwin 伪 TTY 的函数实现一个包装器。
这个想法是获取与给定文件描述符关联的管道的名称。该NtQueryInformationFile
函数获取FILE_NAME_INFORMATION
结构,其中FileName
成员包含管道名称。如果管道名称与以下模式匹配,则该命令很可能在交互模式下运行:
\\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
)。
所以 Cygwin 部分已经被黑了。下一步是从 C 代码创建 Java 函数。
\n\n下面创建带有通过 Java 本机接口 (JNI) 实现的方法的ttyjni.TestApp
类。该代码在 GNU/Linux ( ) 和 Windows 7(64 位)上的 Cygwin 上istty()
进行了测试。x86_64
该代码可以轻松移植到 Windows ( cmd.exe
),甚至可以按原样运行。
所需组件
\n\nx86_64-w64-mingw32-gcc
带编译器的Cygwin布局
\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\nttyjni_TestApp.h
/* 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\nttyjni/TestApp.java
\n\npackage 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\nmake\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