更安全地替代MATLAB的`system`命令

时间:2014-05-30 06:14:03

标签: linux matlab

我一直在使用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调用系统命令而不会有输入损坏的风险?

2 个答案:

答案 0 :(得分:4)

哇。这种行为令人惊讶。值得向MathWorks报告错误的声音。我在OS X上测试了它,看到了相同的行为。

作为一种变通方法,您可以使用对system()的调用以及Matlab中嵌入的JVM中的相关对象重新实现java.lang.Process

你需要:

  • 使用轮询来保持Matlab自己的输入,特别是Ctrl-C,live
  • 使用shell处理而不是直接将命令传递给ProcessBuilder以支持~和其他变量和通配符的扩展,并支持在Matlab系统的单个字符串中指定命令及其参数。 **或者,如果您想要更低级别的控制并且不想处理shell的转义和引用字符串,则可以公开arguments-array形式。两者都很有用。
  • 将输出重定向到文件,或者在轮询代码中定期排空子进程的输出缓冲区。

这是一个例子。

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保持活动状态并将中断该功能,让子进程被杀死。

答案 1 :(得分:3)

感谢Andrew Janke帮助我找到这个解决方案。

为了轻松重现错误,我们可以运行命令:

[ret, out] = system('sleep 2');

如果我们在运行时输入一些字符,out变量将会被我们输入的内容所污染。

此问题的解决方案是从/ dev / null重定向stdin,如下所示:

[ret, out] = system('sleep 2 < /dev/null');

这会阻止out变量被用户输入污染。

有趣的是,虽然这似乎修复了当前MATLAB会话的原始测试用例(在R2014a OSX和R2013b Linux上测试),但如果我们再做[ret, out] = system('sleep 2');,则输出不再受用户输入的影响。