【问题标题】:Reading parameters from a text file into the workspace将文本文件中的参数读入工作区
【发布时间】:2013-05-29 20:19:08
【问题描述】:

我有一个包含以下信息的文件:

% ---------------------- location details --------------------------
%
% lat : latitude  [minimum = -90, maximum = 90, unit =  
%       degrees north]
% lon : longitude [ minimum = -360, maximum = 360, unit = 
%       deg east]
% z: altitude (above sea level, m)
%---------------------------------------------------------------
% location:
   lat = 54.35
   lon = -2.9833

这是文件的一小部分。

我想将其中一些信息读入 MATLAB,然后这些信息可用于执行一些计算。我想读入 MATLAB 的文件部分是文本文件中未注释的部分,ie 在行首有一个 %,然后变量应该是保存在工作区中。例如,我想要:

lat = 54.35
lon = -2.9833

在工作区中。

我该怎么做呢?我已经阅读了有关 textscanfopen 的信息,尽管在这种情况下这些似乎对我没有帮助。

【问题讨论】:

  • 查看sscanffgetlfopen
  • 您可以通过run 将其作为Matlab 脚本运行或通过逐行evalfgetl 实现。
  • 当你选择File -> Import Data... 你打开Import Wizard。在GUI 中,您可以选择Generate MATLAB code 选项 - 它允许您弄清楚如何以您想要的方式导入您想要的内容。
  • 请注意,像这样使用eval 会使您很容易受到注入。试试eval('!cmd'),想象一下如果你的文件实际上试图描述一种格式而没有适当的注释会发生什么。

标签: matlab text-parsing


【解决方案1】:

为您提供字段名称与参数名称匹配的结构,接受逗号分隔的列表。列出所有应该在 char_params 中保留为字符串的参数

char_params={};
fid = fopen(filename);
% Load lines into cell (1x1) containing cell array s (Nx1), 
% skipping lines starting with % and cutting off anything after % in a line
s   = textscan(fid,'%s', 'CommentStyle','%','Delimiter','%');
fclose(fid);
% access the lines strings s{1}, split across '=' and remove whitespace on     both sides
s=strtrim(split(s{1},'='));
% Interpret parameters and save to structure
for ind=1:length(s)
%    User says which parameters are strings
    if any(strcmpi(s{ind,1},char_params))
        file_struct.(s{ind,1})=s{ind,2};
    % Otherwise, assume they are numbers or numeric row arrays
    else
%         remove parentheses and brackets
        trim_s=regexprep(s(ind,2),'[[]()]','');
%         convert comma-separated lists into row arrays
        file_struct.(s{ind,1})=str2double(split(trim_s{1},',')).';
    end
end

【讨论】:

    【解决方案2】:

    快速而肮脏的方法

    我能想到的读取此文件的最简单的解决方案确实使用了textscan :) 并且由于这些行是用有效的 MATLAB 语法方便地编写的,因此您可以稍后使用eval 来评估它们。首先将每一行读取为一个字符串(忽略标题中的 cmets)

    fid = fopen(filename);
    C = textscan(fid, '%s', 'Delimiter', '', 'CommentStyle', '%')
    fclose(fid);
    

    然后将这些行一一输入eval 以在 MATLAB 工作区中创建变量:

    cellfun(@eval, C{1});
    

    它的作用是将该行解释为 MATLAB 命令,创建变量在文件中命名并分配适当的值。如果要抑制eval的输出,可以用evalc代替“吸收输出”:

    cellfun(@evalc, C{1}, 'UniformOutput', false);
    

    这应该适用于您的基本示例,但如果您有多个任何参数的实例,它将失败。另请注意,eval 系列的速度非常慢。

    更稳健的方法

    如果文件结构中的行具有<i>parameter name</i> = <i>number</i> 模式,则可以更智能地读取这些行:

    fid = fopen(filename);
    C = textscan(fid, '%[^= ]%*[= ]%f', 'CommentStyle', '%')
    fclose(fid);
    

    模式中的%[^= ] 匹配第一个字符,直到第一个空格或等号。 %*[ =] 忽略等号和任何尾随空格,然后数值与%f 匹配。生成的单元格数组C 将参数名称存储在第一个单元格中,并将它们的对应值存储在第二个单元格中。

    现在由您来处理解析的数据。例如,要提取latlon 的所有值,您可以这样做:

    lat = C{2}(strcmp(C{1}, 'lat'));
    lon = C{2}(strcmp(C{1}, 'lon'));
    

    如果您有多个“纬度”行,lat 将是一个包含所有这些值的数组。

    【讨论】:

    • 逐行调用具有误导性,因为 MATLAB 文档清楚地使用 while fgetl() 方法识别逐行 I/O。此外,您的方法将覆盖lat/lon
    • @OlegKomarov 我称它为“逐行”,因为它逐行读取行,我认为这没有什么误导性。我不太明白您关于覆盖的评论的第二部分。
    • 尝试重复文件更改第二对纬度/经度的示例,eval() 只需用最新值覆盖,而evalc() 则捕获 cmd 窗口输出,但是现在有额外的间距。更不用说性能了,即使是一个小文件也会很糟糕,而且不是数据馈送的可行解决方案。
    • @OlegKomarov 感谢您的评论。似乎我们对文件结构的解释不同,因为我的印象是每个参数只有一个实例。我的显然是一对lat/lon 的解决方案(并且不声称在这方面以性能为导向)。但是,根据您的评论,我添加了一个更快、更强大的替代方案来解决这些问题。
    【解决方案3】:

    您提供的示例表现不错,因此以下解决方案可能需要进行一些调整。但是,我会推荐它反对任何eval()

    % Read whole file ignoring lines that start with '%' and using '=' as delimiter
    fid = fopen('test.txt');
    s   = textscan(fid,'%s%f', 'CommentStyle','%','Delimiter','=');
    fclose(fid);
    
    % Identify lines with latitude and those with longitude
    idxLat = strncmpi('lat',s{1},3);
    idxLon = strncmpi('lon',s{1},3);
    
    % Store all latitudes and longitudes
    lat = s{2}(idxLat);
    lon = s{2}(idxLon);
    

    【讨论】:

      【解决方案4】:

      这是另一种快速而肮脏的方式:

      fp = fopen('foo.txt');
      
      found = 1;
      while ~feof(fp)
          line = fgetl(fp);
          if (line(1) ~= '%') && ischar(line)
              value(found) = sscanf(line,'%*s %*s %f');
              found = found + 1;
          end
      end
      

      %*s 会跳过“lat”或“long”以及“=”。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-22
        • 2014-01-06
        • 2017-07-16
        • 2020-01-20
        相关资源
        最近更新 更多