3. 批处理任务的数据组织
批处理任务的核心难点往往不在计算本身,而在于“如何把散落在磁盘上的多组输入组织成可循环处理的数据结构,并把结果写到正确的位置”。本节围绕四个相互衔接的问题展开:怎样遍历文件夹、怎样用通配符筛选文件、怎样用元胞数组和结构体承载多组输入、怎样自动生成输出路径。这四步构成了批处理数据组织的完整链条。
一、文件夹遍历:从目录树到文件清单
MATLAB 提供两类遍历方式。dir 返回结构体数组,每个元素含 name、folder、bytes、isdir、datenum 等字段,适合单层目录;dir('**/*.csv') 形式的递归通配符可一次性获取多层目录下的文件。若需要更精细的控制(例如跳过隐藏目录、按深度限制),可用 genpath 生成路径列表后逐个 dir,或使用 fileattrib 判断属性。
一个常见陷阱是 dir 结果中始终包含 '.' 和 '..' 两个伪目录项。若直接对结果循环,会把它们当作文件处理。稳妥做法是先过滤:
files = dir(fullfile(rootDir, '**', '*.csv'));
files = files(~[files.isdir]); % 去掉目录项
files = files(~startsWith({files.name}, '.')); % 去掉隐藏文件
这里 {files.name} 把结构体数组的 name 字段一次性提取为元胞数组,是后续通配符匹配和排序的基础。
二、通配符匹配:筛选与命名约定
通配符 * 匹配任意字符序列,? 匹配单个字符。在批处理中,通配符通常承担“按命名约定分组”的职责。例如实验数据命名为 exp01_run1.csv、exp01_run2.csv、exp02_run1.csv,可用 exp01_*.csv 取出第一组实验的全部重复运行。
当命名规则更复杂时,regexp 或 regexpi 比通配符更合适。例如从文件名中提取实验号和运行号:
tok = regexp(name, 'exp(\d+)_run(\d+)\.csv', 'tokens', 'once');
expId = str2double(tok{1}); runId = str2double(tok{2});
需要强调:通配符匹配的是文件名,不解析语义。若同一目录下混有 exp01_run1.csv 与 exp01_run1_backup.csv,exp01_*.csv 会同时命中两者。因此建议在筛选后增加显式校验,例如检查 bytes > 0、检查字段数是否一致,避免把备份或临时文件带入计算。
三、元胞数组与结构体:承载多组输入
元胞数组适合“同质但长度不一”的输入集合,例如每个被试的多个试次文件路径;结构体适合“带标签的异构字段”,例如每个实验同时包含输入路径、参数、期望输出名。两者常组合使用:外层用结构体数组描述每个实验,内层用元胞数组存放该实验的多个文件。
tasks = struct('expId', {}, 'files', {}, 'params', {});
for k = 1:numel(expIds)
tasks(k).expId = expIds(k);
tasks(k).files = fileLists{k}; % 元胞数组
tasks(k).params = struct('fs', 1000, 'win', 0.5);
end
这种“结构体数组 + 元胞字段”的组织方式有两个好处:一是循环时用 for t = 1:numel(tasks) 即可,索引清晰;二是每个任务的参数与文件绑定在一起,不会因为循环变量错位而串组。若任务数量很大,可考虑用 table 替代结构体数组,便于按列筛选和排序。
四、输出路径自动生成:命名规则与目录创建
输出路径必须与输入一一对应,否则结果无法回溯。推荐用 fullfile 拼接路径、用 sprintf 生成带前导零的编号,避免手工拼接字符串时漏掉分隔符或编号位数不一致:
outDir = fullfile(rootDir, 'out', sprintf('exp%02d', tasks(t).expId));
if ~exist(outDir, 'dir'); mkdir(outDir); end
outFile = fullfile(outDir, sprintf('run%d_result.mat', runId));
这里 %02d 保证 exp01、exp02 编号对齐,排序时不会出现 exp10 排在 exp2 前面的问题。exist(...,'dir') 判断目录是否存在,不存在则 mkdir,这是批处理中防止“第一次运行报错、第二次才成功”的标准做法。
mkdir 会自动创建中间目录,无需逐级判断。但要注意:mkdir 在目录已存在时不会报错,因此 exist 判断并非必需,加上它主要是为了逻辑清晰和避免不必要的系统调用。五、完整组织流程与校验
把上述四步串起来,一个稳健的批处理数据组织流程如下:先用 dir 递归获取候选文件,过滤伪目录项和隐藏文件;再用通配符或正则按命名约定分组,提取实验号与运行号;然后把每组输入封装为结构体数组,每个元素绑定文件列表和参数;最后在循环内根据实验号自动生成输出目录和文件名,并在循环结束后校验输出文件数量是否等于任务数乘以每任务输出数。
校验环节常被忽略,但它决定了批处理是否“可交付”。例如预期 2 个实验 × 2 次运行 = 4 个结果文件,若实际只有 3 个,说明某个任务中途失败或文件名冲突被覆盖。此时应检查 tasks 的长度、每个 tasks(t).files 的元素个数,以及输出目录中 dir('*.mat') 的数量,三者对齐才算完成。
cd 切换工作目录来简化路径拼接。批处理中频繁 cd 会让相对路径的含义随循环变化,一旦某次 cd 失败,后续所有路径都会错位。始终用 fullfile 构造绝对路径,是更安全的做法。总结来说,文件遍历解决“有哪些输入”,通配符与正则解决“哪些输入属于同一组”,元胞数组与结构体解决“如何把多组输入绑定成可循环的任务”,输出路径自动生成解决“结果写到哪里、如何与输入对应”。四者配合,批处理任务才能从“能跑一次”变成“可重复、可校验、可交付”。