java - 为什么 Java CRLF token 不适用于批处理文件输入?

标签 java windows batch-file process pipe

背景:
我曾经回答过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/

相关文章:

java - 从数字中删除小数点

windows - 将脚本字典保存在内存中,VBA

windows - 如何使用批处理文件将多个命令插入到批处理文件中

batch-file - 从另一个 .bat 文件调用 .BAT 文件

windows - 在 "if" block 中设置变量

java - GWT 代码卡在reactToMessages 上

java - 在 Android 中更快地序列化

java - 如何从 JVM 堆栈跟踪知道它处于 GC 暂停状态

c++ - 使用 OpenCV 减去流离失所的掩码

c++ - ListView 中的 Syslink