如何在Matlab中通过命令行运行特定的单元格部分?

5

我正在使用Matlab脚本(称为foo.m)手动循环遍历各种单元格:

%%
%Code for cell 1

%%
%Code for cell 2

从Matlab的命令行中,我希望能够有选择地运行第二个单元格中的代码。但文档中只有交互式操作的说明(例如,将光标放在适当的单元格中,然后进行操作)。我需要一些命令行代码,以便我可以像foo.runCell(1)这样运行上面第一个单元格中的代码。
如果没有办法实现,我将把单元格拆分为单独的脚本/函数。虽然这样不太方便,但我目前处于“快速原型开发”模式,因此希望将所有内容都放在一个文件中。

1
如果有一种从命令行运行特定代码部分的方法,我会感到惊讶。这样做依赖于您知道要运行的代码部分的编号,而不一定需要在编辑器中打开文件... 这将非常容易出错。更清晰的做法是为每个代码部分定义一个函数;然后您就会确切地知道自己正在运行什么。 - jub0bs
当我和你处于相同的模式时,我会给我的单元格设置非常明确的标题(双%%后面放置的文本将在编辑器中以粗体突出显示),然后我混合在控制台中输入/尝试的内容和在编辑器中执行完整单元格的内容。如果我得到一系列满意的命令,我就从命令历史记录中拖动它到编辑器中,创建一个新单元格,给它一个好名字,这样我以后可以轻松找到它,然后继续我的下一段代码。 - Hoki
2
你可能可以做到这一点(虽然我不知道如何做),但这可能涉及编写各种额外的辅助代码,并深入了解Matlab图形用户界面的操作。你可以使用for循环结合条件语句(if...then或switch)来代替,就像在Shell脚本中一样。这可能需要一些努力,但远远不及浏览深奥的Matlab文档来得多。 - Buck Thorn
@Jubobs 是的,我在问题的最后一段提到了这个选项,可以将它们拆分成不同的函数/脚本。我试图避免这样做,但这可能是最好的方法。 - eric
1
我同意@TryHard的观点 - 如果使用java-gui函数,这件事情是可能实现的,但是很可能需要你“模拟”打开文件、将光标移动到正确的单元格并点击执行单元格按钮。这是一个不错的MATLAB gui练习,但我怀疑它的实用性(正如Jubobs所提到的原因)。我认为更容易的方法是找到一种从函数内部访问根工作区的方法,或者只是在全局范围内使用全局变量(这两种方法对我来说听起来都不好...)如果你想要的东西不难,我会看一下gui库... - Dev-iL
显示剩余2条评论
3个回答

3
这是我丑陋的代码,它可以完成你所要求的功能。
尽管我已经尽力了,但我无法找到不模拟按键就能正常工作的方法。我希望这将有助于其他人寻找正确的解决方案。无论如何,让我们开始吧: runCodeSectionInFile.m
function runCodeSectionInFile(scriptFullPath,sectionNum)
%// TIP: You can use "which('scriptName')" to obtain the full path of a 
%// script on your MATLAB path.
%%% // Temporary Workaround
import java.awt.Robot;   %// part of temporary workaround
import java.awt.event.*; %// part of temporary workaround
RoboKey = Robot;         %// part of temporary workaround
RoboKey.setAutoDelay(10);%// part of temporary workaround
%% // Test if the needed components are available (optional)
if ~matlab.desktop.editor.isEditorAvailable || ...
   ~com.mathworks.mde.editor.codepad.Codepad.isCodepadEnabled
    error('MATLAB editor is N\A');
end
%% // Open and\or switch to the script file
%// Test if script is opened:
if ~matlab.desktop.editor.isOpen(scriptFullPath) 
    scriptDoc = matlab.desktop.editor.openDocument(scriptFullPath);
else %// in case the script is open, get a handle to it, and save it:
    scriptDoc = matlab.desktop.editor.findOpenDocument(scriptFullPath);
    %// Save the script before running (optional):
    scriptDoc.save;
end
scriptDoc.goToLine(0); %// Position the cursor at the beginning of the file
                       %// NOTE1: - uses zero based indexing!
jEd = com.mathworks.mlservices.MLEditorServices.getEditorApplication ...
        .openEditorForExistingFile(java.io.File(scriptFullPath));
jEd.getTextComponent.grabFocus; drawnow; %// part of temp fix
%// NOTE2: most of the above can be replaced with:
%//   EDITOROBJ = matlab.desktop.editor.openAndGoToLine(FILENAME,LINENUM);
%% // Get the Codepad and the LineManager handles:
jCm = com.mathworks.mde.editor.codepad.CodepadActionManager ...
                   .getCodepadActionManager(jEd);
jCp = jEd.getProperty('Codepad');
jLm = jCp.getLineManager(jEd.getTextComponent,jCm);
%% // Advance to the desired section
jAc = com.mathworks.mde.editor.codepad.CodepadAction.CODEPAD_NEXT_CELL;
                                           %// 'next-cell' Action

for ind1=1:sectionNum-1 %// click "advance" several times
    %// <somehowExecute(jAc) OR jCp.nextCell() >    
    RoboKey.keyPress(KeyEvent.VK_CONTROL); %// part of temporary workaround
    RoboKey.keyPress(KeyEvent.VK_DOWN);    %// part of temporary workaround
end
RoboKey.keyRelease(KeyEvent.VK_DOWN);      %// part of temporary workaround
RoboKey.keyRelease(KeyEvent.VK_CONTROL);   %// part of temporary workaround
%% Execute section - equivalent to clicking "Run Section" once

jAc = com.mathworks.mde.editor.codepad.CodepadAction.CODEPAD_EVALUATE_CELL; 
                                                 %// 'eval-cell' Action
%// <somehowExecute(jAc); OR jCp.evalCurrentCell(true) >    
RoboKey.keyPress(KeyEvent.VK_CONTROL); %// part of temporary workaround
RoboKey.keyPress(KeyEvent.VK_ENTER);   %// part of temporary workaround
RoboKey.keyRelease(KeyEvent.VK_CONTROL);   %// part of temporary workaround
%% // Close the file (optional)
jEd.close;
%% // Return focus to the command line:
com.mathworks.mde.cmdwin.CmdWin.getInstance.grabFocus;

testScript.m:

%% // This is code block one
disp('This is section one');
%% // This is code block two
disp('This is section two');
%% // This is code block three
disp('This is section three');

要运行演示程序,只需将两个文件放在同一个文件夹中,确保它在您的路径上(或cd到该文件夹),然后执行runCodeSectionInFile(which('testScript.m'),1)。这将产生以下结果:

>> runCodeSectionInFile(which('testScript.m'),1)
This is section one
>> runCodeSectionInFile(which('testScript.m'),2)
This is section two
>> runCodeSectionInFile(which('testScript.m'),3)
This is section three

您可能需要调整RoboKey.setAutoDelay(10);的值为一个更大的数,以避免出现NPE错误。
参考资料:
  1. 一篇已经消失的MATLAB博客文章
  2. 我曾提出的一个问题和它的回答
注:
我是在2014b上写的。其他(更旧)版本可能需要进行调整...

Edit1:

我通过找到Codepad对象及其TaggedLineManager的句柄,成功地解决了这个问题。目前的问题在于大多数Codepad方法必须在EDT上运行(类似于“UI线程”,即源自与界面的交互)。

非常出色,但希望我可以接受两个答案。评论处理非常顺畅。 - eric

3
< p > Dev-iL 使用java等提供了一个好的答案...... 在这里,我提供另一种方法,它不使用java或编辑器,而是在请求时读取文件并评估语句。

为了使其工作,它需要先保存文件(这不是交互式运行单元/代码块的先决条件,我很感激)。

无论如何,我认为这是一个“奇怪”的问题,并且想尝试回答。

这是我的脚本(cellScript.m),其中包含代码块(请注意,我已经在“%%”后面给每个块命名):

%Note: for this method your NOT allowed to put comments at the end of lines
%% cellA
disp ( 'running cell A' ); 
disp ( 'finished cell A' );
%% cellB
disp ( 'running cell B' );
disp ( 'finished cell B' );
%% cellC
disp ( 'running cell C' );
for ii=1:100
  aa(ii) = randi(1);
end
% cells can have comments
disp ( 'past 1st coment' );
   % cells comments can be indented
disp ( 'past indented comment' );

  %{
   block 
   comment
  %}
disp ( 'past block comment' );

% cells can have comments
% multiple lines of comments
  % not lined up
%
%and empty

disp ( 'past multiple lines of comments' );

disp ( 'finished cell C' );
%% cellD
disp ( 'running cell D' );

disp ( 'finished cell D' );
%% cellE
disp ( 'running cell E' );
disp ( 'finished cell E' );

我创建了一个类来完成所需的工作(我将其称为cellRunner.m)。
classdef cellRunner < handle
  properties ( SetAccess = private )
    fileName
    fileInfo
    cellInfo
    cellNames = {};
  end
  methods 
    function obj = cellRunner ( file ) % constructor
      if nargin == 0                        
        obj.fileName = 'cellScript.m';      % default file for testing
      else
        obj.fileName = file;                % store user file
      end
      obj.parseFile();                      % read the file into memory
    end
    function obj = parseFile ( obj )
      if ~isempty ( obj.fileInfo )                        % on parsing check to see if its been parsed before
        if isequal ( obj.fileInfo, dir ( obj.fileName ) ) % Check date stamp (has cell file been modified
%           disp ( 'file not changed - reading skipped' );  % if not skip
%           reading 
          return
        end
      end
      obj.fileInfo = dir ( obj.fileName );                % store file info
      fid = fopen ( obj.fileName );                       % open file for reading
      if fid ~= -1
        index = 0;                                        % this is the index of each cell
        inCell = false;                                   % has it found a cell to start reading
        lines = cell(0);                                  
        while ( true )
          line = fgetl ( fid );                           % read the line in the file
          if line == -1; break; end                       % check for the end of the file
          sLine = strtrim ( line );                       % trim any white space
          if length ( sLine ) > 2 && strcmp ( sLine(1:2), '%%' ) % check to see if its the start of a cell
            if index > 0                                  % Store the last cell data                
              obj.cellInfo{index} = lines;                % in class to run when required
            end
            index = index + 1;                            % increment the index
            obj.cellNames{index} = strtrim ( sLine(3:end) ); % save the name of the cell
            lines = cell(0);                              % re-initialise the lines var
            inCell = true;                                % the start of the cells have been found
          elseif inCell                                   % if reading a cell array
            lines{end+1} = line;                          % add each line to the lines var
          end          
        end
        if index > 0                                      % make sure and save the last cell when finished reading
          obj.cellInfo{index} = lines;
        end
        fclose ( fid );
      else
        error ( 'cellRunner:fileError', 'unable to read file' );
      end
    end
    function obj = runCell ( obj, arg )
      % obj.runCell ( 'cellName' );
      % obj.runCell ( index );
      obj.parseFile();                                    % check that the file hasn't been changed
      if ischar ( arg )                                   % if user provided a char then search for it
        index = strcmp ( arg, obj.cellNames );            % find the index
        if ~any ( index )                                 % check it was found
          error ( 'cellRunner:notFound', '%s not found', arg ); 
        end
      else
        index = arg;                                      % if index is an integer (not checked - assumed if not char)
        if index < 1 || index > length ( obj.cellInfo )   % check integer is valid
          error ( 'cellRunner:notFound', 'Index %d not found', arg );
        end
      end
      commands = obj.cellInfo{index}{1};                  % start to build the command to execute.
      inBlock = false;
      for ii=2:length(obj.cellInfo{index})                % loop around - ignoring any commented lines.
        nextLine = strtrim ( obj.cellInfo{index}{ii} ); 
        if inBlock
          if length ( nextLine ) == 2 && strcmp ( nextLine, '%}' );
            inBlock = false;
          end
          continue
        end
        if length ( nextLine ) == 2 && strcmp ( nextLine, '%{' );
          inBlock = true;
          continue
        end
        if length ( nextLine ) >= 1 && strcmp ( nextLine(1), '%' )
          continue;
        end
        commands = sprintf ( '%s;%s', commands, obj.cellInfo{index}{ii} ); % build a parge string to eval
      end
      evalin('base',commands);                            % eval the expression in the base workspace.
    end
  end
end

代码随后按以下方式使用:
obj.cellRunner();
% Individual cells can be run in two ways:

% By providing the name of the cell (the string after the %%)
obj.runCell ( 'cellC' );
% By providing the index
obj.runCell ( 3 );

注意:为使此功能生效,文件必须被保存。

示例运行:

whos
obj = cellRunner ( 'cellScript.m' );
obj.runCell ( 'cellC' );
running cell C
past 1st coment
past indented comment
past block comment
past multiple lines of comments
finished cell C
whos
  Name      Size             Bytes  Class         Attributes

  aa        1x100              800  double                  
  ans       1x1                112  cellRunner              
  ii        1x1                  8  double                  
  obj       1x1                112  cellRunner              

注1 - 为什么要使用handle类?我从handle类继承,因为我只想要已读取的文件数据的一个副本 - 参见此问题中答案1,了解何时使用值/句柄类的优秀概述。


确实是一个有趣的替代方案。顺便说一下,也许你应该解释一下为什么扩展了 handle,因为对于一些读者来说可能不太清楚... - Dev-iL
我添加了一条关于为什么要继承handle类的注释。 - matlabgui
@matlabgui 界面美观且功能强大。问题:初始调用不应该是类似于 obj = cellRunner('filename.m') 这样的吗? - eric
@matlabgui 看起来它卡在某些被注释的行上(例如,如果“%comment here”在独立行上,则跳过文件的其余部分)。我正在检查代码,看看能否修复它... - eric
你不能在代码行末添加注释,例如:yourCommand % your comment由于它使用了evalin的方式,所以这种写法是不被允许的。 - matlabgui
显示剩余3条评论

2
为了使用命令行运行文件中的特定部分,你可以使用以下方法:
echodemo(filename, index of section)

MATLAB文档

这是有关MATLAB的文档。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接