用于读取配置文件和解析指令的C代码

时间:2018-01-24 01:18:49

标签: c

我正在尝试读取配置文件并解析配置指令。到目前为止,我有以下代码,我需要有关如何改进或更改它的建议。这有效吗?谢谢!

struct config
{
    char host;
    char port;
}

void parse_line(char *buf) {
    char *line;
    if(strstr(buf, "host=") || strstr(buf, "host = ") || strstr(buf, "host= ") || strstr(buf, "host =")) {
        line = strstr(buf, "=");
        printf("Host: %s", &line[2]);
    } else if(strstr(buf, "port=") || strstr(buf, "port = ") || strstr(buf, "port= ") || strstr(buf, "port =")) {
        line = strstr(buf, "=");
        printf("Port: %s", &line[2]);
    }
}

int main(int argc, char *argv[])
{
    char *file_name;
    FILE *file;
    file_name = argv[1];
    file = fopen(file_name, "r");

    // check if file is NULL, etc..

    char buffer[BUFSIZ];
    char *line;
    int i;
    while(fgets(buffer, sizeof(buffer), file) != NULL) {
        for(i = 0; i < strlen(buffer); i++) { // iterate through the chars in a line
            if(buffer[i] == '#') { // if char is a #, stop processing chars on this line
                break;
            } else if(buffer[i] == ' ') { // if char is whitespace, continue until something is found
                continue;
            } else {
                parse_line(buffer); // if char is not a # and not whitespace, it is a config directive, parse it
                break;
            }
        }
    }

    fclose(file);
    return 0;
}

我正在寻找一种忽略#的方法,如果它是一行中的第一个字符,还有白色空格的行。我认为我的代码可以做到这一点,但效率如何?

修改

感谢大家提出的所有建议,我设法完成了这个简单的代码来修剪空格,这样我就不需要所有strstr()次调用。

void trim(char *src)
{
    int i, len;
    len = strlen(src);

    for(i = 0; i < len; i++) {
        if(src[i] == ' ') {
            continue;
        }
        if(src[i] == '\n' || src[i] == '#') {
            break;
        }
        printf("%c", src[i]); // prints: host=1.2.3.4
    }
}

int main(void)
{
    char *str = "host =  1.2.3.4 # this is a comment\n";
    trim(str);
    return EXIT_SUCCESS;
}

它正确打印:host=1.2.3.4但现在我需要在变量中进行进一步解析。我想我会尝试使用strcpy

编辑2:

我不认为strcpy是正确的选择。这些字符在循环中打印出来,因此每次使用strcpy时,前一个字符都会被覆盖。我试过这个,但它没有用,因为只有host=部分被放入arr。 IP部分未放入arr ..如何修复..

char arr[sizeof(src)];

for(i = 0; i < len; i++) {
    if(src[i] == ' ') {
        continue;
    }
    if(src[i] == '\n' || src[i] == '#') {
        break;
    }
    printf("%c", src[i]); // prints: host=1.2.3.4
    arr[i] = src[i];
}

int j;
for(j = 0; j < sizeof(arr); j++) {
    printf("%c", arr[j]); //prints: host=
}

编辑3:

我找到了将字符放入arr的正确方法:

int i, count = 0;
for(i = 0; i < len; i++) {
    if(src[i] == ' ') {
        continue;
    }
    if(src[i] == '\n' || src[i] == '#') {
        break;
    }
    arr[count] = src[i];
    count++;
}

4 个答案:

答案 0 :(得分:1)

有几种方法可以提高效果:

  1. 在这种情况下调用strstr()效率很低,因为每次调用strstr()时都可以检查一次buf的“host”部分,而不是多次。而是创建一个if语句,检查buf是否以“host”开头,然后检查buf是否包含其他元素。同样的事情适用于检查“port”存在的代码部分。

  2. 在main中的循环中,而不是这样做:

    for(i = 0; i < strlen(buffer); i++) { // iterate through the chars in a line
    if(buffer[i] == '#') { // if char is a #, stop processing chars on this line
        break;
    } else if(buffer[i] == ' ') { // if char is whitespace, continue until something is found
        continue;
    } else {
        parse_line(buffer); // if char is not a # and not whitespace, it is a config directive, parse it
        break;
    }
    
  3. 这样做:

    for(i = 0; i < strlen(buffer); i++) { // iterate through the chars in a line
        char temp = buffer[i];
        if(temp == '#') { // if char is a #, stop processing chars on this line
            break;
        } else if (temp != ' ') {
            parse_line(buffer); // if char is not a # and not whitespace, it is a config directive, parse it
            break;
        }
    

    检查某些东西是否与另一个东西不相等可能与检查它们是否相等一样快(至少在英特尔,je(跳跃等于)和jne(跳跃不相等)指令表现出相同的延迟每个1个周期),所以没有必要使用continue语句。 temp变量是这样的,如果第一个if为false,则不需要在第二个中计算buffer [i]。另外,执行下面所述的user3121023(与创建临时变量的性能相同)。

    1. 您可以使用特定于操作系统的功能(例如Windows上的WINAPI / WIN32 / WIN64(同义词)库中的thos)而不是C标准库函数。 Microsoft在MSDN(Microsoft Developer Network)网站上提供了有关其功能的非常好的文档。

    2. 在主机和端口上执行操作时,使用uint_fast8_t(在stdint.h中定义,此typedef设置为大于或等于typedef中指定的位大小的最快整数类型) (但是在将变量存储在磁盘上时使用字符,以便更快地进行读取i / o操作。)

    3. 这与性能无关,但在main中使用return EXIT_SUCCESS;而不是return 0;,因为使用EXIT_SUCCESS更具可读性并具有相同的性能。

答案 1 :(得分:1)

我认为parse_line对我的品味有点刻板,我会使用strtok 代替。那么你不必过多担心空间,就像你做的那样 在=标志前有一个空格。

您的struct也有误,hostport只会包含一个字符。 除port之外应该是整数。然后你需要一个分号; 结构定义。

struct config
{
  char host[100];
  int port;
};

int parse_line(struct config *config, char *buf)
{
    if(config == NULL || buf == NULL)
        return 0;

    char varname[100];
    char value[100];
    const char* sep = "=\n"; // get also rid of newlines
    char *token;

    token = strtok(buf, sep);

    strncpy(varname, token, sizeof varname);
    varname[sizeof(varname) - 1] = 0; // making sure that varname is C-String

    trim(varname);

    token = strtok(NULL, sep);

    if(token == NULL)
    {
        // line not in format var=val
        return 0;
    }

    strncpy(value, token, sizeof value);
    value[sizeof(varname) - 1] = 0

    trim(value);

    if(strcmp(varname, "port") == 0)
    {
        config->port = atoi(value);
        return 1;
    }


    if(strcmp(varname, "host") == 0)
    {
        strncpy(config->host, value, siezof config->host);
        config->host[(sizeof config->host) - 1] = 0;
        return 1;
    }


    // var=val not recognized
    return 0;
}

请注意,我使用了一个名为trim的函数。这个功能不属于 标准库。下面我发布了这样一个函数的可能实现。

我喜欢使用trim,因为它摆脱了空格。现在你可以做到这一点 main

struct config config;
// initializing
config.port = 0;
config.host[0] = 0;

int linecnt = 0;

while(fgets(buffer, sizeof(buffer), file) != NULL) {
    linecnt++;
    trim(buffer);
    if(buffer[0] == '#')
        continue;

    if(!parse_line(&config, buffer))
    {
        fprintf(stderr, "Error on line %d, ignoring.\n", linecnt);
        continue;
    }
}

trim

的可能实施方式
void rtrim(char *src)
{
    size_t i, len;
    volatile int isblank = 1;

    if(src == NULL) return;

    len = strlen(src);
    if(len == 0) return;
    for(i = len - 1; i > 0; i--)
    {   
        isblank = isspace(src[i]);
        if(isblank)
            src[i] = 0;
        else
            break;
    }   
    if(isspace(src[i]))
        src[i] = 0;
}

void ltrim(char *src)
{
    size_t i, len;

    if(src == NULL) return;

    i = 0;
    len = strlen(src);
    if(len == 0) return;
    while(src[i] && isspace(src[i]))
                i++;

    memmove(src, src + i, len - i + 1); 
    return;
}

void trim(char *src)
{
    rtrim(src);
    ltrim(src);
}

答案 2 :(得分:1)

老实说,我无法帮助但是想知道滚动你自己的解析器是否真的很棒。

为什么不使用现有的JSON或YAML解析器并测试解析数据中的键?

通过允许以非常小的功能添加新密钥,这将很容易扩展,配置文件的通用格式使开发人员可以轻松编辑。

如果您要推出自己的解析器,那么前面提到的一些建议很有意义。

最大的问题是:不要寻找整个缓冲区,请阅读您面前的单行并报告任何错误。此外,随时前进

如果有人将GigaByte垃圾转储到配置文件中,你的解析器应该可以正常工作,所以不要假设数据。

答案 3 :(得分:0)

您的实施非常脆弱。解析器确实应该在看到意外情况时验证语法并返回错误。例如,您的应该检测丢失的字段并乘以定义的字段。

幸运的是,这个解析问题很简单,sscanf可以处理所有事情:

  • 跳过空行,
  • 跳过评论
  • 忽略任何数量的空白
  • 提取键/值对

这是代码:

#include <stdio.h>

#define CONFIG_SIZE (256)
#define HOST_SET (1)
#define PORT_SET (2)

typedef struct config {
  unsigned set;
  char host[CONFIG_SIZE];
  unsigned long port;
} CONFIG;

// Parse the buffer for config info. Return an error code or 0 for no error.
int parse_config(char *buf, CONFIG *config) {
  char dummy[CONFIG_SIZE];
  if (sscanf(buf, " %s", dummy) == EOF) return 0; // blank line
  if (sscanf(buf, " %[#]", dummy) == 1) return 0; // comment
  if (sscanf(buf, " host = %s", config->host) == 1) {
    if (config->set & HOST_SET) return HOST_SET; // error; host already set
    config->set |= HOST_SET;
    return 0;
  }
  if (sscanf(buf, " port = %lu", &config->port) == 1) {
    if (config->set & PORT_SET) return PORT_SET; // error; port already set
    config->set |= PORT_SET;
    return 0;
  }
  return 3; // syntax error
}

void init_config(CONFIG *config) {
  config->set = 0u;
}

void print_config(CONFIG *config) {
  printf("[host=%s,port=", config->set & HOST_SET ? config->host : "<unset>");
  if (config->set & PORT_SET) printf("%lu]", config->port); else printf("<unset>]");
}

int main(int argc, char *argv[]) {
  if (argc != 2) {
    fprintf(stderr, "Usage: %s CONFIG_FILE\n", argv[0]);
    return 1;
  }
  FILE *f = fopen(argv[1], "r");
  char buf[CONFIG_SIZE];
  CONFIG config[1];
  init_config(config);
  int line_number = 0;
  while (fgets(buf, sizeof buf, f)) {
    ++line_number;
    int err = parse_config(buf, config);
    if (err) fprintf(stderr, "error line %d: %d\n", line_number, err);
  }
  print_config(config);
  return 0;
}

使用此输入:

# This is a comment

This isn't
   # Non-leading comment
host = 123.456.789.10

 ###
port =42

port=    1
host=fruit.foo.bar

输出

error line 3: 3
error line 10: 2
error line 11: 1
[host=fruit.foo.bar,port=1]

请注意,当解析器发现已设置字段时,它仍然使用配置中的最新值。保持原始状态很容易。我会让你玩得那么开心。

相关问题