读取c中由tab分隔的文件

时间:2015-04-06 17:47:18

标签: c fread scanf

我是C的新手,阅读文件让我疯狂...... 我想读一个包括姓名,出生地和电话号码等的文件。所有这些都用标签分隔

格式可能是这样的:

Bob Jason   Los Angeles    33333333
Alice Wong  Washington DC  111-333-222

所以我创建了一个结构来记录它。

typedef struct Person{
    char name[20];
    char address[30];
    char phone[20];
} Person;

我尝试了很多方法将这个文件读入struct但它失败了。 我厌倦了:

read_file = fopen("read.txt", "r");
Person temp;
fread(&temp, sizeof(Person), 100, read_file);
printf("%s %s %s \n", temp.name, temp.address, temp.phone);

但char字符串没有记录到由tab分隔的temp中,它将整个文件读入temp.name并获得奇怪的输出。

然后我尝试了fscanf和sscanf,那些都不适合分离标签

fscanf(read_file, "%s %s %s", temp.name, temp.address, temp.phone);

或者

fscanf(read_file, "%s\t%s\t%s", temp.name, temp.address, temp.phone);

这将字符串按空格分开,所以我分别得到鲍勃和杰森,而实际上,我需要得到鲍勃杰森"作为一个字符串。当我创建文本文件时,我确实通过制表符分隔了这些格式。

对于sscanf来说,我多次试过不同的方式......

请帮忙......

4 个答案:

答案 0 :(得分:2)

我建议:

  1. 使用fgets逐行阅读文字。
  2. 使用strtok分隔行的内容,方法是使用制表符作为分隔符。

  3. // Use an appropriate number for LINE_SIZE
    #define LINE_SIZE 200
    char line[LINE_SIZE];
    
    if ( fgets(line, sizeof(line), read_file) == NULL )
    {
       // Deal with error.
    }
    
    Person temp;
    char* token = strtok(line, "\t");
    if ( token == NULL )
    {
       // Deal with error.
    }
    else
    {
       // Copy token at most the number of characters
       // temp.name can hold. Similar logic applies to address
       // and phone number.
    
       temp.name[0] = '\0';
       strncat(temp.name, token, sizeof(temp.name)-1);
    }
    
    token = strtok(NULL, "\t");
    if ( token == NULL )
    {
       // Deal with error.
    }
    else
    {
       temp.address[0] = '\0';
       strncat(temp.address, token, sizeof(temp.address)-1);
    }
    
    token = strtok(NULL, "\n");
    if ( token == NULL )
    {
       // Deal with error.
    }
    else
    {
       temp.phone[0] = '\0';
       strncat(temp.phone, token, sizeof(temp.phone)-1);
    }
    

    <强>更新

    使用辅助函数,可以减小代码的大小。 (谢谢@chux)

    // The helper function.
    void copyToken(char* destination,
                   char* source,
                   size_t maxLen;
                   char const* delimiter)
    {
        char* token = strtok(source, delimiter);
        if ( token != NULL )
        {
           destination[0] = '\0';
           strncat(destination, token, maxLen-1);
        }
    }
    
    // Use an appropriate number for LINE_SIZE
    #define LINE_SIZE 200
    char line[LINE_SIZE];
    
    if ( fgets(line, sizeof(line), read_file) == NULL )
    {
       // Deal with error.
    }
    
    Person temp;   
    copyToken(temp.name, line, sizeof(temp.name), "\t");
    copyToken(temp.address, NULL, sizeof(temp.address), "\t");
    copyToken(temp.phone, NULL, sizeof(temp.phone), "\n");
    

答案 1 :(得分:1)

这仅用于演示,有更好的方法来初始化变量,但为了说明您的主要问题,即读取由制表符分隔的文件,您可以编写如下函数:

假设有严格的字段定义和结构定义,您可以使用strtok()获取令牌。

//for a file with constant field definitions
void GetFileContents(char *file, PERSON *person)
{
    char line[260];
    FILE *fp;
    char *buf=0;
    char temp[80];
    int i = -1;

    fp = fopen(file, "r");
    while(fgets(line, 260, fp))
    {
        i++;
        buf = strtok(line, "\t\n");
        if(buf) strcpy(person[i].name, buf);
        buf = strtok(NULL, "\t\n");
        if(buf) strcpy(person[i].address, buf);
        buf = strtok(NULL, "\t\n");
        if(buf) strcpy(person[i].phone, buf);
        //Note:  if you have more fields, add more strtok/strcpy sections
        //Note:  This method will ONLY work for consistent number of fields.
        //If variable number of fields, suggest 2 dimensional string array. 
    }
    fclose(fp);
}

在main()中调用它:

int main(void)
{
    //...
    PERSON person[NUM_LINES], *pPerson;  //NUM_LINES defined elsewhere
                                         //and there are better ways
                                         //this is just for illustration
    pPerson = &person[0];//initialize pointer to person

    GetFileContents(filename, pPerson);  //call function to populate person.
    //...
    return 0;
}

答案 2 :(得分:0)

首先,

fread(&temp, sizeof(temp), 100, read_file);

不起作用,因为字段不是固定宽度,因此name的{​​{1}} 30将始终读取20个字符,依此类推,这并不总是正确的事情。

您需要一次读取一行,然后解析该行,您可以使用您喜欢的任何方法来阅读,一个简单的方法是使用address这样的

fgets()

现在我们需要一个函数来解析行并将数据存储在char line[100]; Person persons[100]; int index; index = 0; while (fgets(line, sizeof(line), read_file) != NULL) { persons[i++] = parseLineAndExtractPerson(line); } struct instance

Person

这是一个循环的示例实现,最多可读取char *extractToken(const char *const line, char *buffer, size_t bufferLength) { char *pointer; size_t length; if ((line == NULL) || (buffer == NULL)) return NULL; pointer = strpbrk(line, "\t"); if (pointer == NULL) length = strlen(line); else length = pointer - line; if (length >= bufferLength) /* truncate the string if it was too long */ length = bufferLength - 1; buffer[length] = '\0'; memcpy(buffer, line, length); return pointer + 1; } Person parseLineAndExtractPerson(const char *line) { Person person; person.name[0] = '\0'; person.address[0] = '\0'; person.phone[0] = '\0'; line = extractToken(line, person.name, sizeof(person.name)); line = extractToken(line, person.address, sizeof(person.address)); line = extractToken(line, person.phone, sizeof(person.phone)); return person; } 个记录

100

这是一个完整的程序,可以完成我认为你需要的程序

int main(void)
 {
    char   line[100];
    Person persons[100];
    int    index;
    FILE  *read_file;

    read_file = fopen("/path/to/the/file.type", "r");
    if (read_file == NULL)
        return -1;
    index = 0;
    while ((index < 100) && (fgets(line, sizeof(line), read_file) != NULL))
     {
        size_t length;

        /* remove the '\n' left by `fgets()'. */            
        length = strlen(line);
        if ((length > 0) && (line[length - 1] == '\n'))
            line[length - 1] = '\0';
        persons[index++] = parseLineAndExtractPerson(line);
     }
    fclose(read_file);
    while (--index >= 0)
        printf("%s: %s, %s\n", persons[index].name, persons[index].address, persons[index].phone);
    return 0;
 }

答案 3 :(得分:0)

解析fgets返回的字符串可能非常烦人,尤其是在输入被截断时。事实上,fgets还有很多不足之处。你得到了正确的字符串还是更多?最后有换行吗?就此而言,距离最后20个字节还是32768个字节?如果你不需要计算那么多字节两次 - 一次使用fgets而一次使用strlen,那就更好了,只是为了删除你不想要的换行符。

fscanf之类的东西在这种情况下不一定按预期工作,除非你有C99&#34; scanset&#34;功能可用,如果你有足够的空间,那将自动添加一个空终止符。任何scanf家庭的返回值都是您确定成功或失败的朋友。

您可以使用%NNc来避免空终结符,其中NN是宽度,但如果\t字节中有NN,那么您需要将它分开并将其移动到下一个字段,除非意味着必须将下一个字段中的字节移动到该字段之后的字段,并且第90个字段将需要将其字节移动到第91个字段...并且希望您只需要需要做一次......显然,这实际上也不是一个解决方案。

鉴于这些原因,我觉得直到您遇到一个预期的分隔符并且让您在指定的大小对于空终止符来说太小而且足够大时决定函数的行为会更容易阅读填补你的缓冲区。无论如何,这是代码。我认为这很简单:

/*
 * Read a token.
 *
 * tok: The buffer used to store the token.
 * max: The maximum number of characters to store in the buffer.
 * delims: A string containing the individual delimiter bytes.
 * fileptr: The file pointer to read the token from.
 *
 * Return value:
 *   - max: The buffer is full. In this case, the string _IS NOT_ null terminated.
 *          This may or may not be a problem: it's your choice.
 *   - (size_t)-1: An I/O error occurred before the last delimiter
 *                 (just like with `fgets`, use `feof`).
 *   - any other value: The length of the token as `strlen` would return.
 *                      In this case, the string _IS_ null terminated.
 */
size_t
read_token(char *restrict tok, size_t max, const char *restrict delims,
    FILE *restrict fileptr)
{
        int c;
        size_t n;

        for (n = 0; n < max && (c = getchar()) != EOF &&
            strchr(delims, c) == NULL; ++n)
                *tok++ = c;

        if (c == EOF)
            return (size_t)-1;

        if (n == max)
            return max;

        *tok = 0;

        return n;
}

用法非常简单:

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

typedef struct person {
        char    name[20];
        char    address[30];
        char    phone[20];
} Person;

int
main(void)
{
        FILE *read_file;
        Person temp;
        size_t line_num;
        size_t len;
        int c;
        int exit_status = EXIT_SUCCESS;

        read_file = fopen("read.txt", "r");
        if (read_file == NULL) {
                fprintf(stderr, "Error opening read.txt\n");
                return 1;
        }

        for (line_num = 0;; ++line_num) {
                /*
                 * Used for detecting early EOF
                 * (e.g. the last line contains only a name).
                 */
                temp.name[0] = temp.phone[0] = 0;

                len = read_token(temp.name, sizeof(temp.name), "\t",
                    read_file);
                if (len == (size_t)-1)
                        break;
                if (len == max) {
                        fprintf(stderr, "Skipping bad line %zu\n", line_num + 1);
                        while ((c = getchar()) != EOF && c != '\n')
                                ;       /* nothing */
                        continue;
                }

                len = read_token(temp.address, sizeof(temp.address), "\t",
                    read_file);
                if (len == (size_t)-1)
                        break;
                if (len == max) {
                        fprintf(stderr, "Skipping bad line %zu\n", line_num + 1);
                        while ((c = getchar()) != EOF && c != '\n')
                                ;       /* nothing */
                        continue;
                }

                len = read_token(temp.phone, sizeof(temp.phone), "\t",
                    read_file);
                if (len == (size_t)-1)
                        break;
                if (len == max) {
                        fprintf(stderr, "Skipping bad line %zu\n", line_num + 1);
                        while ((c = getchar()) != EOF && c != '\n')
                                ;       /* nothing */
                        continue;
                }

                // Do something with the input here.  Example:
                printf("Entry %zu:\n"
                    "\tName:    %.*s\n"
                    "\tAddress: %.*s\n"
                    "\tPhone:   %.*s\n\n",
                    line_num + 1,
                    (int)sizeof(temp.name), temp.name,
                    (int)sizeof(temp.address), temp.address,
                    (int)sizeof(temp.phone), temp.phone);
        }

        if (ferror(read_file)) {
                fprintf(stderr, "error reading from file\n");
                exit_status = EXIT_FAILURE;
        }
        else if (feof(read_file) && temp.phone[0] == 0 && temp.name[0] != 0) {
                fprintf(stderr, "Unexpected end of file while reading entry %zu\n",
                    line_num + 1);
                exit_status = EXIT_FAILURE;
        }
        //else feof(read_file) is still true, but we parsed a full entry/record

        fclose(read_file);
        return exit_status;
 }

注意如何在读取循环中出现完全相同的8行代码来处理read_token的返回值?因此,我认为可能有另外一个函数来调用read_token并处理其返回值,允许main简单地调用这个&#34; read_token处理程序&# 34;,但我认为上面的代码为您提供了有关如何使用read_token以及它如何适用于您的情况的基本概念。如果你愿意的话,你可以用某种方式改变行为,但是上面的read_token函数在使用这样的分隔输入时会很适合我(当你在混合中添加引用的字段时,事情会有点复杂,但据我所知,并没有那么复杂。您可以决定返回max会发生什么。我选择将其视为错误,但您可能会另有想法。您甚至可以在getchar时添加额外的n == max,并认为max是一个成功的返回值,而(size_t)-2是&#34;令牌太大&#34;错误指示器。