我正在尝试读取配置文件并解析配置指令。到目前为止,我有以下代码,我需要有关如何改进或更改它的建议。这有效吗?谢谢!
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++;
}
我认为parse_line
对我的口味有点刻板,我会用strtok
代替。那么你不必过多担心空间,就像你在=
标志前有空格一样。
你的struct
也是错的,host
和port
只会有一个角色。除了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);
}
有几种方法可以提高性能:
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;
}
做这个:
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;
}
检查某些东西是否与另一个不相等可能与检查它们是否相等一样快(至少在Intel上,je(跳跃相等)和jne(跳跃不相等)指令表现出相同的1个周期延迟每个),所以没有必要继续声明。 temp变量是这样的,如果第一个if为false,则不需要在第二个中计算buffer [i]。另外,执行下面所述的user3121023(与创建临时变量相同的性能原因)。
uint_fast8_t
(在stdint.h中定义,此typedef设置为大于或等于typedef中指定的位大小的最快整数类型)(但在将变量存储在主机上时使用chars)磁盘,以便更快地进行读取i / o操作)。return EXIT_SUCCESS;
而不是return 0;
,因为使用EXIT_SUCCESS更具可读性并具有相同的性能。老实说,我不禁想知道滚动你自己的解析器是不是很棒。
为什么不使用现有的JSON或YAML解析器并测试解析数据中的键?
通过允许以非常小的努力添加新密钥,这将很容易扩展,并且配置文件的通用格式使开发人员可以非常轻松地进行编辑。
如果你打算推出自己的解析器,那么前面提到的一些建议很有意义。
最大的问题是:不要寻找整个缓冲区,读取前面的单行并报告任何错误。此外,随你前进。
如果有人将GigaByte垃圾转储到配置文件中,您的解析器应该可以正常工作,因此不要对数据做出任何假设。
你的实现非常脆弱。解析器确实应该在看到意外情况时验证语法并返回错误。例如,您的应该检测丢失的字段并乘以定义的字段。
幸运的是,这个解析问题很简单,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]
请注意,当解析器发现已经设置了一个字段时,它仍然使用配置中的最新值。保持原始状态很容易。我会让你玩得那么有趣。