比 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调用系统命令而不冒输入损坏的风险?

哇。这种行为令人惊讶。听起来值得作为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变量将被我们输入的内容污染。

解决这个问题的方法是从/dev/null重定向stdin,如下所示:
[ret, out] = system('sleep 2 < /dev/null');

这样可以防止out变量被用户输入污染。

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

最新更新