我一直在使用MATLAB的system
命令来获得一些linux命令的结果,如以下简单示例:
[junk, result] = system('find ~/ -type f')
按预期工作,除非用户同时输入MATLAB的命令窗口。在长find
命令期间,这种情况并不少见。如果发生这种情况,那么用户的输入似乎与find
命令的结果混淆了(然后事情就中断了)。
例如,而不是:
/path/to/file/one
/path/to/file/two
/path/to/file/three
/path/to/file/four
我可能会得到:
J/path/to/file/one
u/path/to/file/two
n/path/to/file/three
k/path/to/file/four
为了更容易地演示,我们可以运行如下命令:
[junk, result] = system('cat')
在命令窗口中键入一些内容,然后按CTRL+D关闭流。result
变量将是您在命令窗口中输入的任何内容。
是否有一种更安全的方式让我从MATLAB调用系统命令而不冒输入损坏的风险?
哇。这种行为令人惊讶。听起来值得作为bug报告给MathWorks。我在OS X上测试了一下,看到了同样的行为。
作为一种解决方案,您可以使用调用java.lang.Process
和嵌入在Matlab中的JVM中的相关对象来重新实现system()
。
你需要:
- 使用轮询来保持Matlab自己的输入,特别是Ctrl-C,实时
- 使用shell处理而不是直接将命令传递给ProcessBuilder,以支持
~
和其他变量和通配符的扩展,并支持像Matlab系统那样在单个字符串中指定命令及其参数。另外,如果您想要低级控制并且不想为shell处理转义和引用字符串,您可以公开参数数组形式。都是有用的。 - 将输出重定向到文件,或者在轮询代码中定期耗尽子进程的输出缓冲区。
下面是一个例子。
function [status,out,errout] = systemwithjava(cmd)
%SYSTEMCMD Version of system implemented with java.lang features
%
% [status,out,errout] = systemwithcmd(cmd)
%
% Written to work around issue with Matlab UI entry getting mixed up with
% output captured by system().
if isunix
% Use 'sh -s' to enable processing of single line command and expansion of ~
% and other special characters, like the Matlab system() does
pb = java.lang.ProcessBuilder({'bash', '-s'});
% Redirect stdout to avoid filling up buffers
myTempname = tempname;
stdoutFile = [myTempname '.systemwithjava.out'];
stderrFile = [myTempname '.systemwithjava.err'];
pb.redirectOutput(java.io.File(stdoutFile));
pb.redirectError(java.io.File(stderrFile));
p = pb.start();
RAII.cleanUpProcess = onCleanup(@() p.destroy());
RAII.stdoutFile = onCleanup(@() delete(stdoutFile));
RAII.stderrFile = onCleanup(@() delete(stderrFile));
childStdin = java.io.PrintStream(p.getOutputStream());
childStdin.println(cmd);
childStdin.close();
else
% TODO: Fill in Windows implementation here
end
% Poll instead of waitFor() so Ctrl-C stays live
% This try/catch mechanism is lousy, but there is no isFinished() method.
% Could be done more cleanly with a Java worker that did waitFor() on a
% separate thread, and have the GUI event thread interrupt it on Ctrl-C.
status = [];
while true
try
status = p.exitValue();
% If that returned, it means the process is finished
break;
catch err
if isequal(err.identifier, 'MATLAB:Java:GenericException') ...
&& isa(err.ExceptionObject, 'java.lang.IllegalThreadStateException')
% Means child process is still running
% (Seriously, java.lang.Process, no "bool isFinished()"?
% Just continue
else
rethrow(err);
end
end
% Pause to allow UI event processing, including Ctrl-C
pause(.01);
end
% Collect output
out = slurpfile(stdoutFile);
errout = slurpfile(stderrFile);
end
function out = slurpfile(file)
fid = fopen(file, 'r');
RAII.fid = onCleanup(@() fclose(fid));
out = fread(fid, 'char=>char')'; %'
end
我尽我所能地尝试了这个,看起来它使子进程的输出与键盘输入分离到Matlab IDE。键盘输入被缓冲起来,并在systemwithjava()
返回后作为附加命令执行。Ctrl-C保持有效,并将中断该函数,让子进程被杀死。
感谢Andrew Janke帮助我找到这个解决方案。
为了容易地重现错误,我们可以运行命令:
[ret, out] = system('sleep 2');
如果我们在运行时输入一些字符,out
变量将被我们输入的内容污染。
[ret, out] = system('sleep 2 < /dev/null');
这样可以防止out
变量被用户输入污染。
有趣的是,虽然这似乎修复了当前MATLAB会话的原始测试用例(在R2014a OSX和R2013b Linux上测试),所以如果我们做另一个[ret, out] = system('sleep 2');
,输出不再受到用户输入的污染。