背景:
我曾经回答过this question
那是关于将两个输入字符串从 Java 进程刷新到批处理脚本。由于我找到了解决方法,所以我仍然
非常有兴趣解决剩余的谜团并找出为什么明显的解决方案不起作用。
问题描述
请看这个非常简单的批处理脚本:
@ECHO OFF
SET /P input1=1st Input:
SET /P input2=2nd Input:
ECHO 1st Input: %input1% and 2nd Input: %input2%
如果您使用 ProcessBuilder
通过 Java 运行此批处理脚本并将两个输入字符串刷新到其中,您会注意到只有
第一个输入字符串将被消耗,而第二个将被忽略。
我发现 SET/P
命令在
- 找到 CRLF token
- 超时
- 全缓冲区(1024字节)
我接受的解决方法是基于最后两个选项,通过在输入之间使用 Thread.sleep(100)
语句或使用 1024 字节
每个输入的缓冲区。
它总是适用于单个输入或在这种情况下是第一个输入,因为关闭流会产生效果
批处理脚本读取一个输入并为空返回所有以下 SET/P
语句。
问题
为什么使用 CRLF token 的第一个选项 "input\r\n"
不起作用?
研究
我已经尝试通过使用 \x0d
和 \x0a
创建一个字节缓冲区来解决 String.getBytes()
方法
CRLF token 的字节,但它没有任何效果。
我尝试了所有其他 OutputStream
包装器,例如 PrintWriter
来检查是否有
flush()
实现没有任何成功的问题。
我创建了一个 C++ 程序,它通过使用 CreateProcess
基本上与 java 程序执行相同的操作,而且很神奇,它运行起来非常棒。
测试代码
不工作的 Java 代码:
ProcessBuilder builder = new ProcessBuilder("test.bat");
Process process = builder.start();
OutputStream out = process.getOutputStream();
out.write("foo\r\n".getBytes());
out.flush();
out.write("bar\r\n".getBytes());
out.flush();
out.close();
BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = in.readLine()) != null)
System.out.println(line);
in.close();
完整的 C++ 代码:
DWORD dwWritten;
char cmdline[] = "test.bat";
CHAR Input1[] = "foo\r\n";
CHAR Input2[] = "bar\r\n";
HANDLE hStdInRd = NULL;
HANDLE hStdInWr = NULL;
SECURITY_ATTRIBUTES saAttr;
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
// Create Pipe
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
CreatePipe(&hStdInRd, &hStdInWr, &saAttr, 0);
SetHandleInformation(hStdInWr, HANDLE_FLAG_INHERIT, 0);
// Create Process
ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION));
ZeroMemory( &siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
siStartInfo.hStdInput = hStdInRd;
siStartInfo.dwFlags |= STARTF_USESTDHANDLES;
CreateProcess(NULL, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo);
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
// Write to Pipe
WriteFile(hStdInWr, Input1, (DWORD)strlen(Input1), &dwWritten, NULL);
FlushFileBuffers(hStdInWr);
WriteFile(hStdInWr, Input2, (DWORD)strlen(Input2), &dwWritten, NULL);
FlushFileBuffers(hStdInWr);
CloseHandle(hStdInWr);
又是这个问题
这个问题对我来说没有任何意义,并且很困扰我。为什么从 Java 发送 CRLF token 没有任何
从 C++ 程序发送时对批处理文件输入有影响吗?
最佳答案
关于“put/p”以及Windows操作系统上的管道和子进程
只是为了测试,我稍微扩展了你的测试批处理以获得四个输入而不是两个 现在看看这个不错的测试
>type test.txt | test.bat
1st Input:2nd Input:3rd Input:4th Input:1st Input: one and 2nd Input: and 3rd
Input: and 4rd Input:
"--"
>test.bat < test.txt
1st Input:2nd Input:3rd Input:4th Input:1st Input: one and 2nd Input: two and
3rd Input: three and 4rd Input: four
"--"
这里有趣的是,第一个示例与 java 代码完全一样(只有第一个“set/P”接收一个值,而第二个按预期工作
更有趣的是,如果您像这样在批处理文件中的某处放置一行:wmic Process >> TestProcesses.txt
通过检查 TestProcesses.txt,在我的环境中,我可以看到第一个方法(管道)存在 cmd.exe C:\Windows\system32\cmd.exe/S/D/c"test.bat "
当我们使用第二个(重定向)时不存在
我用完了来自 java 的新测试批处理(包括 wmic 诊断);当我们检查 TestProcesses 时,我们应该看到两个不同的进程:
java.exe java -cp .\build\classes javaappcrlf.JavaAppCRLF
cmd.exe C:\Windows\system32\cmd.exe /c C:\projects\JavaAppCRLF\test.bat
与第一种方法(管道)一样,我们有一个单独的批处理过程,其中“put/p”不起作用
来自文章Pipes and CMD.exe一章Pipes and CMD.exe
This has several side effects: Any newline (CR/LF) characters in the batch_command will be turned into & operators. see StackOverflow If the batch_command includes any caret escape characters ^ they will need to be doubled up so that the escape survives into the new CMD shell.
关于堆栈溢出的链接文章也很有趣
关于C++测试
我对中描述的 c++ 程序做了一点改动
Creating a Child Process with Redirected Input and Output只是读取一个四行的文件,并将其内容传递给一个子进程,该子进程通过 pipe
执行我们的批处理,结果与您的 Java 程序相同
替代重构/解决方法
从上面提到的调查结果来看,java 程序读取和写入(临时) 文件(...我知道这不是一回事)应该有效;我通过这种方式更改构建器成功地测试了一个可行的解决方案
ProcessBuilder builder = new ProcessBuilder(
"cmd",
"/c",
"(C:\\projects\\JavaAppCRLF\\test4.bat < C:\\projects\\JavaAppCRLF\\tmp-test4.in)",
">",
"C:\\projects\\JavaAppCRLF\\tmp-test4.out"
);
Post Scriptum:关于其他 shell 的有趣说明(即:“os x”或 linux 上的 bash)
据我所知,并非所有其他平台都以同样的方式遭受这个“问题”; IE。 在 bash(os x 终端)上,我使用脚本进行了以下测试,该脚本的作用与我们之前在 Windows 下的测试一样:
cd ~/projects/so-test/java-crlf-token/JavaAppCRLF
$ cat test.sh
#!/bin/bash -
# SET /P input1=1st Input:
echo -n "1st Input:";
read input1;
#SET /P input2=2nd Input:
echo -n "2nd Input:";
read input2;
#ECHO 1st Input: %input1% and 2nd Input: %input2%
echo -n "1st Input: ${input1} and 2nd Input: ${input2}"
那么唯一改成java程序的就是引用脚本:
ProcessBuilder builder = new ProcessBuilder("/Users/userx/projects/so-test/java-crlf-token/JavaAppCRLF/test.sh");
让我们看看会发生什么:
$ cat test.txt
abc
cde
# :pipe
$ cat test.txt | test.sh
$ cat test.txt | ./test.sh
1st Input:2nd Input:1st Input: abc and 2nd Input: cde
# :redirection
$ ./test.sh < test.txt
1st Input:2nd Input:1st Input: abc and 2nd Input: cde
# :java
$ java -cp build/classes/ javaappcrlf.JavaAppCRLF
1st Input:2nd Input:1st Input: foo
and 2nd Input: bar
关于java - 为什么 Java CRLF token 不适用于批处理文件输入?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40341728/