Search code examples
cformattingprintfvariadic-functions

Printing a variable number of arguments to a format string


Given a format string, a counter variable for the number of specifiers and an array of the strings to be inputted, how could this be printed?

Here's an example:

char *format_str = "str(%s)ing(%s)";
int count = 2;
char **specs = { [0] = "rts", [1] = "gni" };

So, the list of strings aligns respectively with the ordering of specifiers. When printed, the end result would be:

"str(rts)ing(gni)"

Could a function be written to print such a string with any format string & any number of specifiers & respective arguments? I have tried to do this using strtok(), vsprintf, snprintf etc, but I still cannot get it quite right.

EDIT: To clarify, format_str contains count number of specifiers and the array specs contains count number of strings. The proposed function would therefore should print count number of strings into format_str.


Solution

  • As others said there is no direct way of doing that. You can build your own function which dumps the values of strings at the correct format specifiers. Below function makes a temporary format string for each %s and appends it to the earlier build string using snprintf().

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define MAXBUF      4096
    
    char *strmaker(char* format, int num_args, char** strings)
    {
        char* prnt = calloc(sizeof(char), MAXBUF);
        int prnt_ct = 0;
        char* tmp_fmt = malloc(strlen(format) + 1); // Prepare for the worst case (format == tmp_fmt).
        int fmt_ct = 0;
    
        /* Append the strings to the prnt buffer */
    
        for (int i = 0; i < num_args; i++) {
            char* s_loc = strstr(format + fmt_ct, "%s");    // Search the format-string for string specifier (%s)
            if (s_loc == NULL)
                return prnt;
    
            int tmp_fmt_len = (int) (s_loc + 2 - format - fmt_ct);  // +2 for %s
            strncpy(tmp_fmt, format + fmt_ct, tmp_fmt_len); // Make tmp_fmt
            tmp_fmt[tmp_fmt_len] = '\0';
            fmt_ct = fmt_ct + tmp_fmt_len;
    
            int p_return = snprintf(prnt + prnt_ct, MAXBUF - prnt_ct, tmp_fmt, strings[i]);   // If no error, return the number characters printed excluding nul (man page)
    
            if (p_return >= MAXBUF - prnt_ct)   // If buffer overflows (man page)
                return prnt;
    
            prnt_ct = prnt_ct + p_return;   // Update the index location.
        }
    
        return prnt;
    }
    
    int main(int argc, char *argv[]) // Pass format and arguments
    {
        if (argc <= 1)
           return -1;
    
        char *s = strmaker(argv[1], argc - 2, argv + 2);
        printf("%s\n", s);
        free(s);
    
        return 0;
    }
    

    Terminal Session:

    $ ./a.out '%s %s %s' 1 2 3 
    1 2 3
    $ ./a.out 'one %s two %s three %s' 1 2 3 
    one 1 two 2 three 3
    $ ./a.out 'one %s two %s three' 1 2 3 
    one 1 two 2
    $ ./a.out 'one %s two %s three %s' 1 2 
    one 1 two 2