为什么Java CRLF令牌不适用于批处理文件输入



背景:
我曾经回答过这个问题这是关于将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令牌
  • 超时
  • 按满缓冲区(1024字节(

我接受的解决方法是基于最后两个选项,在输入之间使用Thread.sleep(100)语句或使用1024字节每个输入的缓冲区。

它总是适用于单个输入,或者在这种情况下适用于第一个输入,因为关闭流会产生效果批处理脚本读取一个输入并且为空返回所有后续的CCD_ 4语句。

问题
为什么使用CRLF令牌"inputrn"的第一个选项不起作用?

研究
我已经尝试过通过使用x0dx0a创建字节缓冲区来解决String.getBytes()方法CRLF令牌的字节,但它无效。

我尝试了所有其他OutputStream包装器,如PrintWriter,以检查是否存在CCD_ 11实现的一个问题没有取得任何成功。

我使用CreateProcess创建了一个基本上与java程序相同的C++程序,它的工作原理很有魅力。

测试代码
不工作的Java代码:

ProcessBuilder builder = new ProcessBuilder("test.bat");
Process process = builder.start();
OutputStream out = process.getOutputStream();
out.write("foorn".getBytes());
out.flush();
out.write("barrn".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[] = "foorn";
CHAR Input2[] = "barrn";
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令牌没有从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:Windowssystem32cmd.exe /S /D /c" test.bat"

我从java中运行了新的测试批(包括wmic诊断(;当我们检查测试过程时,我们应该看到两个不同的过程:

java.exe              java  -cp .buildclasses javaappcrlf.JavaAppCRLF
cmd.exe               C:Windowssystem32cmd.exe /c C:projectsJavaAppCRLFtest.bat  

与第一种方法(管道(一样,我们对批次有一个单独的过程,其中"put/p"不起的作用

来自Pipes and CMD.exe 一文的管道和CMD.exe一章

这有几个副作用:batch_command将变成&操作员。请参阅StackOverflow Ifbatch_command包含任何插入符号转义符^,它们将需要加倍,以便转义能够保存到新的CMD中壳

关于堆栈溢出的链接文章也是有趣的

关于C++测试

我对中描述的c++程序做了一些更改创建一个具有重定向输入和输出的子进程只是为了读取一个四行的文件,并将其内容传递给一个子进程,该子进程通过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的一个有趣的注释(例如:在"osx"或linux上的bash(

AFAIK并非所有其他平台都以同样的方式遇到了这个"问题";即。在bash(osx终端(上,我用一个脚本进行了以下测试,该脚本与我们之前在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

最新更新