基于格式字符串的智能可变扩展

时间:2009-04-04 07:58:02

标签: c string tokenize variadic

我有一个守护进程,它读取配置文件以便知道在哪里写东西。在配置文件中,存在这样的行:

output = /tmp/foo/%d/%s/output

或者,它可能看起来像这样:

output = /tmp/foo/%s/output/%d

......或者只是这样:

output = /tmp/foo/%s/output

......或者最后:

output = /tmp/output

我的程序中有cfg-> pathfmt这一行。我现在要做的是想出一些聪明的方法来使用它。

稍作解释一下,路径最多可以包含两个要格式化的组件。 %d将作为作业ID(int)扩展,%s将作为作业名称(字符串)扩展。用户可能希望在配置文件中使用一个,两个或不使用。在我最终将它传递给snprintf()之前,我需要知道他们想要什么,以什么顺序。我可以把它缩小范围,但我一直想和strtok()谈谈,这看起来很难看。

我想为用户提供这种灵活性,但是我很想找到一种合理,便携的方式来实现它。对于如何开始寻找这个问题,我也完全失败了。

如果:

,我会非常高兴
  • 有人可以帮我缩小搜索短语以找到好的例子
  • 有人可以发布指向实施此
  • 的OSS项目的链接
  • 有人可以发布一些伪造的代码

我不希望为我编写的代码,我只是真的坚持(我认为)应该是非常简单的东西,并需要一些帮助第一口。我真的觉得我在思考并忽略了明显的事情。

最终结果应该是这样的布尔函数:

bool output_sugar(const char *fmt, int jobid, const char *jobname, struct job *j);

然后在j-> outpath上调用snprintf()(明智地),如果某种垃圾(即%后跟不是s,d或%的东西)在配置行(或其null)中则返回false 。理智检查很容易,我只是有点时间来获取格式正确的参数数量(和顺序)。

提前致谢。此外,如果您有这样的声誉,请随时编辑此标题,正如我所说,我不太确定如何在一行中提出问题。我认为我需要的是一个parser,但使用一个完整的词法分析器/解析器处理一个简单的字符串感觉很尴尬。

3 个答案:

答案 0 :(得分:8)

是的,你需要某种解析器。不过,它并不复杂:

void format_filename(const char *fmt, int jobid, const char *jobname,
                     char *buffer, size_t buflen)
{
    char *end = buffer + buflen - 1;
    const char *src = fmt;
    char *dst = buffer;
    char c;
    assert(buffer != 0 && fmt != 0 && buflen != 0 && jobname != 0);
    while ((c = *src++) != '\0')
    {
        if (dst >= end)
            err_exit("buffer overflow in %s(): format = %s\n",
                     __func__, fmt);
        else if (c != '%')
            *dst++ = c;
        else if ((c = *src++) == '\0' || c == '%')
        {
            *dst++ = '%';
            if (c == '\0')
                break;
        }
        else if (c == 's')
        {
            size_t len = strlen(jobname);
            if (len > end - dst)
                err_exit("buffer overflow on jobname in %s(): format = %s\n",
                         __func__, fmt);
            else
            {
                strcpy(dst, jobname);
                dst += len;
            }
        }
        else if (c == 'd')
        {
             int nchars = snprintf(dst, end - dst, "%d", jobid);
             if (nchars < 0 || nchars >= end - dst)
                 err_exit("format error on jobid in %s(); format = %s\n",
                          __func__, fmt);
             dst += nchars;
        }
        else
            err_exit("invalid format character %d in %s(): format = %s\n",
                     c, __func__, fmt);
    }
    *dst = '\0';
}

现在测试过代码。请注意,它支持'%%'表示法,允许用户在输出中嵌入单个'%'。此外,它将字符串末尾的单个'%'视为有效且等效于'%%'。它在错误时调用err_exit();您可以选择适合您系统的其他错误策略。我只是假设您已包含<assert.h><stdio.h><string.h>以及err_exit()(可变参数)函数的标头。


测试代码......

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>

static void err_exit(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
    exit(1);
}

...然后如上所述format_filename(),然后......

#define DIM(x) (sizeof(x)/sizeof(*(x)))

static const char *format[] =
{
    "/tmp/%d/name/%s",
    "/tmp/%s/number/%d",
    "/tmp/%s.%d%%",
    "/tmp/%",
};

int main(void)
{
    char buffer[64];
    size_t i;

    for (i = 0; i < DIM(format); i++)
    {
        format_filename(format[i], 1234, "job-name", buffer, sizeof(buffer));
        printf("fmt = %-20s; name = %s\n", format[i], buffer);
    }

    return(0);
}

答案 1 :(得分:5)

使用strtok很容易出错。您可以使用(fl)lex和yacc将变量视为迷你语言。 There is simple tutorial here

%{
#include <stdio.h>
%}

%%
%d                      printf("%04d",jobid);
%s                      printf("%s",stripspaces(dirname));
%%

我做了一个ODBC包装器,可以让你做像dbprintf这样的东西(“插入blah值%s%D%T%Y”,这里的东西......);但是很多年前我就点了它并使用strtok解析了格式字符串。

答案 2 :(得分:1)

如果选项的数量很少而您不需要/需要解析器的额外灵活性和复杂性,则可以使用strstr()搜索每个可能的替换子字符串。

如果你只有两个选项,你可以容忍地创建一个四分支的if / else结构(只有A,只有B,两者都带有A之前的B,两者都带有B之前的A),在这里调用sprintf()正确排序的参数。否则,进行多次sprintf()调用,每次调用都只替换格式字符串中的第一个替换标记。 (这意味着要建立一个需要替换的列表,并按照外观顺序对它们进行排序......)