C语言阅读分页文本文件

时间:2016-01-06 17:24:59

标签: c printf scanf

首先让我请求你的原谅,如果这太过于微不足道了,我不是C开发人员,通常是我在Fortran编程。

我需要阅读一些列文本文件。我遇到的问题是某些列可能有空格(非填充值)或没有完全填充字段。

让我用一个问题的简短例子。假设我有一个生成器程序,如:

#include <stdio.h>
#include <stdlib.h>

int main(){

   printf("xxxx%4d%4.2f\n",99,3.14);

}

当我执行这个程序时,我得到:

$ ./t1
xxxx  993.14

如果我将其放入文本文件并尝试使用(例如)sscanf阅读代码:

#include <stdio.h>
#include <stdlib.h>

int main() {

   char *fmt = "%*4c%4d%4f";
   char *line = "xxxx  993.14";
   int  ival;
   float fval;

   sscanf(line,fmt,&ival,&fval);

   printf(">>>>%d|%f\n",ival,fval);
}

结果是:

$ ./t2
>>>>993|0.140000

这是什么问题? sscanf似乎认为所有空间都没有意义,应该被丢弃。所以“% 4c”完成它的意图,它会计算4个字符,而不会丢弃任何空格,并因“”而丢弃所有内容。接下来,%4d开始跳过所有空格,并在找到转换的第一个有效字符时开始计算字段的4个字符。因此,99的值变为993,3.14变为0.14。

在Fortran中,阅读代码为:

program t3

   implicit none

   integer :: ival
   real    :: fval
   character(len=30) :: fmt="(4x,i4,f4.0)"
   character(len=30) :: line="xxxx  993.14"

   read(line,fmt) ival, fval
   write(*,"('>>>>',i4,'|',f4.2)") ival,fval

end program t3

,结果将是:

$ ./t3
>>>>  99|3.14

也就是说,格式规范说明了字段宽度,转换中没有任何内容丢弃,除非“nX”规范指示。

帮助帮助者的最后一些评论:

  1. 要阅读的格式是国际标准,没有 改变它的方法。
  2. 现有文件的数量很大,可以考虑干预或 格式改变。
  3. 它不是CSV或类似格式。
  4. 代码必须在C中才能集成到免费软件包中。

    抱歉太久了,试图尽可能完整地说出问题。

    问题是:有没有办法告诉sscanf不要跳过空格?如果没有,是否有一种简单的方法在C中执行它或者必须为每种记录类型编写专门的解析器?

    提前谢谢。

5 个答案:

答案 0 :(得分:2)

使用sscanf读取固定长度字段时,最好将值解析为字符串(可以通过多种方式进行),然后对每个字段执行独立转换。这允许您基于每个字段处理转换/错误检测。例如,您可以使用格式字符串:

char *fmt = "%*4s%2[^0-9]%s";

将读取/丢弃4个前导字符,然后读取2个字符作为整数,然后读取line的剩余部分(或直到下一个空格)作为包含浮点值的字符串。

要处理line作为固定长度字段的存储和解析,您可以使用临时字符数组来保存每个字符串,然后使用sscanf来填充它们,就像您尝试做的那样用整数和浮点直接。 e.g:

char istr[8] = {0};
char fstr[16] = {0};
...
sscanf (line,fmt,istr,fstr);

注意>在这种情况下,您可以使用istr[3]fstr[7]的最小存储空间,根据需要调整存储长度,但为提供空间nul-terminated 字符)

然后,您可以使用strtolstrtof提供转化功能,并对每个值进行错误检查。例如:

errno = 0;
if ((ival = (int)strtol (istr, NULL, 10)) == 0 && errno)
    fprintf (stderr, "error: integer conversion failed.\n");
    /* underflow/overflow checks omitted */

errno = 0;
if ((fval = strtof (fstr, NULL)) == 0 && errno)
    fprintf (stderr, "error: integer conversion failed.\n");
    /* nan and inf checks omitted */

在你的例子中将所有部分放在一起,你可以使用类似的东西:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main() {

    char *fmt = "%*4s%2[^0-9]%s";
    char *line = "xxxx  993.14";

    char istr[8] = {0};
    char fstr[16] = {0};
    int ival;
    float fval;

    sscanf (line,fmt,istr,fstr);

    errno = 0;
    if ((ival = (int)strtol (istr, NULL, 10)) == 0 && errno)
        fprintf (stderr, "error: integer conversion failed.\n");
        /* underflow/overflow checks omitted */

    errno = 0;
    if ((fval = strtof (fstr, NULL)) == 0 && errno)
        fprintf (stderr, "error: integer conversion failed.\n");
        /* nan and inf checks omitted */

    printf(">>>>%d|%6.2f\n",ival,fval);

    return 0;
}

示例/输出

$ >>>>0|993.14

答案 1 :(得分:1)

*scanf()的目的不是处理固定的列宽,而是插入非插入的空格。

使用sscanf(),为了不跳过空格,代码必须使用"%c""%n""%[]",因为所有其他说明符都会跳过前导空格而这些跳过的字符不会有助于宽度限制。

要扫描现在位于buffer的打印行,请充分利用'\n'的唯一用途是在行尾。

char str_int[5];
char str_float[5];
int n = 0;

sscanf(buffer, "%*4c%4[^\n]%4[^\n]%n", str_int, str_float, &n);
if (n != 12 || buffer[n] != '\n') Fail();
// Now convert str_int, str_float as needed.

使用sscanf()的另一种方法是将buffer解析为

int  ival;
float fval;
if (strlen(buffer) != 13) Fail();
if (sscanf(&buffer[8], "%f", &fval) != 1) Fail();
buffer[8] = '\0';
if (sscanf(&buffer[4], "%d", &ival) != 1) Fail();

注意:以下中的4将输出宽度指定为4个字符。 4是要打印的最小宽度。

printf("xxxx%4d%4.2f\n",ival, fval);

代码可以使用以下方法来检测问题。

if (13 != printf("xxxx%4d%4.2f\n",ival, fval)) Fail();

提防

printf("xxxx%4d%4.2f\n",123, 9.995000001f); // "xxxx 12310.00\n"

答案 2 :(得分:0)

如果每个元素的宽度都是固定的,那么你真的不需要scanf(),试试这个

char copy[5];
const char *line = "xxxx  993.14";
int ival;
float fval;

copy[0] = line[4];
copy[1] = line[5];
copy[2] = line[6];
copy[3] = line[7];
copy[4] = '\0'; // nul terminate for `atoi' to work

ival = atoi(copy);
fval = atof(&line[8]);

fprintf(stdout, "%d -- %f\n", ival, fval);

如果您愿意(可能应该),您可以使用strtol()代替atoi()strtof()代替atof()来检查格式错误的数据

这两个函数都使用参数来存储未转换的 / 无效字符,您可以检查传递的指针以验证转换是否存在问题。< / p>

或者如果您真的希望scanf()做同样的事情,请将整数+空格捕获到char数组,然后将其转换为int,就像这样

char integer[5];
const char *line = "xxxx  993.14";
int ival;
float fval;

if (sscanf(line, "%*4c%4[0-9 ]%f", integer, &fval) != 2)
    return -1;
ival = atoi(integer);

fprintf(stdout, "%d -- %f\n", ival, fval);

格式"%*4c%4[0-9 ]%f"

  1. 跳过前四个字符,包括空格。
  2. 扫描接下来的四个字符,如果它们只包含数字或空格。
  3. 扫描输入字符串的其余部分,搜索匹配的float值。

答案 3 :(得分:0)

首先,我不知道。可能有某种方法来纠缠sscanf以识别整数计数的空白。但我不认为scanf是为这种格式而制作的。该工具试图变得聪明有用,它正在咬你的屁股。

但是如果它是列表数据并且您知道各个字段的位置,那么就可以轻松解决问题。只需提取您想要的字段。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv)
{
  char line[] = "xxxx  893.14";
  char tmp[100];
  int thatDamnNumber;
  float myfloatykins;

  //Get that field
  memcpy(tmp, line+4, 4);
  sscanf(tmp, "%d", &thatDamnNumber);

  //Kill that field so it doesn't goober-up the float
  memset(line+4, ' ', 4);
  sscanf(line, "%*4c%f", &myfloatykins);

  printf("%d %f\n", thatDamnNumber, myfloatykins);

  return 0;

}

如果有很多这样的话,你可以做一些通用函数:integerExtract(int positionStart,int sizeInCharacters),floatExtract()等。

答案 4 :(得分:0)

我发布了我认为从目前为止和其他来源的答案中得出的最终结论。

Fortran中一项非常简单的任务在其他语言中并不是一项如此微不足道的任务。我想 - 不确定 - 同样的任务可以像其他语言中的Fortran一样容易。我认为Cobol,Pascal,PL / I和其他人在穿孔卡片时可能是微不足道的。

我认为现在大多数语言都使用不同的数据结构更加舒适,并从C继承其I / O结构。我认为Java,Python,Perl(?)和其他语言可以作为示例。

从我在本主题中看到的内容中,使用C读取/转换固定列长文本数据有两个主要问题。

第一个问题是,正如菲利普在他的回答中所说的那样:“这个工具试图变得聪明有用,并且正在咬你的屁股。”非常正确!关键是C文本I / O似乎认为“白色空间”类似于NULL字符,应该被丢弃,完全忽略了字段开头的任何信息。唯一的例外似乎是%nc可以获得n个字符,甚至是空格。

第二个问题是转换“标记”(如何调用?)%nf会在找到有效字符时继续转换,即使你说停在第4个字符。

如果我们将这两个问题与一个完全充满空白区域的字段联系起来,取决于所使用的转换工具,它会抛出错误或继续疯狂地寻找有意义的东西。

在一天结束时,似乎唯一的方法是将字段长度提取到另一个内存区域,动态分配与否(我们每个列长度可以有一个区域),并尝试解析这个单独的区域,考虑到全空白区域缓存错误的可能性。