繁体   English   中英

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

[英]Why does the Java CRLF token does not work with batch file inputs?

背景:
我曾经回答过这个问题 ,即将两个输入字符串从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字节缓冲区。

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

这个问题
为什么使用CRLF令牌"input\\r\\n"的第一个选项不起作用?

研究
我已经尝试通过使用\\x0d\\x0a作为CRLF令牌的最后字节创建一个字节缓冲区来解决String.getBytes()方法,但它没有任何效果。

我尝试了所有其他OutputStream包装器,如PrintWriter以检查flush()实现是否存在问题,但没有成功。

我创建了一个C ++程序,它通过使用CreateProcess基本上与java程序相同,并且stangely它就像一个魅力。

测试代码
不工作的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令牌对批处理文件输入没有任何影响,而从C ++程序发送时呢?

关于Windows上的“put / p”以及管道和子进程

只是为了测试我有点扩展你的测试批次以获得四个输入而不是两个现在看看这个不错的测试

>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,在我的enviromnet中,我可以看到第一个方法(管道)存在的情况cmd.exe C:\\Windows\\system32\\cmd.exe /S /D /c" test.bat"当我们使用第二个(重定向)时不存在的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”不起作用

从文章的篇章管道和的CMD.exe 管道和的CMD.exe

这有几个副作用:batch_command中的任何换行符(CR / LF)都将变为&运算符。 请参阅StackOverflow如果batch_command包含任何插入符转义字符^,则需要将它们加倍,以便转义能够存活到新的CMD shell中。

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

关于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的一个有趣的注释(即:bash on“os x”或linux)

AFAIK并非所有其他平台都以同样的方式遭受这个“问题”; 即在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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM