代码之家  ›  专栏  ›  技术社区  ›  Tim Post Samir J M Araujo

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

  •  4
  • Tim Post Samir J M Araujo  · 技术社区  · 16 年前

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

    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项目的链接来实现这个
    • 有人可以发布一些psuedo代码

    我不想让代码为我而写,我只想知道(我认为)应该是一个非常简单的东西,需要一些帮助来吃第一口。我真的觉得我想得太多了,忽略了那些显而易见的东西。

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

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

    然后,它将在j->outpath上调用snprintf()(明智地),如果配置行(或其空值)中存在某种类型的垃圾(即,%后面跟一些不是s、d或%)则返回false。健全性检查很容易,我只是花了点时间来获取参数的数量(和顺序)以正确格式化。

    事先谢谢。另外,如果你有这样的名声,请随意编辑这个标题,正如我说的,我不太确定如何在一行中问这个问题。我想我需要的是 parser 但是使用一个完整的lexer/parser来处理一个简单的字符串会感到很尴尬。

    3 回复  |  直到 11 年前
        1
  •  8
  •   Jonathan Leffler    16 年前

    是的,您需要某种类型的解析器。不过,它不必复杂:

    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);
    }
    
        2
  •  5
  •   ojblass    16 年前

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

    %{
    #include <stdio.h>
    %}
    
    %%
    %d                      printf("%04d",jobid);
    %s                      printf("%s",stripspaces(dirname));
    %%
    

    我做了一个odbc包装器,可以让你做dbprintf之类的事情(“insert into blah values%s%d%t%y”,stufacture here…);但那是很多年前的事了,我用strtok对它进行了位分并解析了格式字符串。

        3
  •  1
  •   Jeff Shannon    16 年前

    如果选项的数量很小,而且您不希望/不需要解析器的额外灵活性和复杂性,那么您可以使用strstr()简单地搜索每个潜在的替换子字符串。

    如果只有这两个选项,则可以相当容易地创建一个四分支if/else结构(只有a,只有b,两者都有a在b之前,都有b在a之前),在该结构中使用正确顺序的参数调用sprintf()。否则,进行多个sprintf()调用,每个调用仅替换格式字符串中的第一个替换标记。(这意味着建立一个需要替换的列表,并按照外观顺序对其进行排序…)