user1424739
user1424739

Reputation: 13685

turning an array into a variable number of arguments

In pseudo code, I want put an arbitrary number of arguments to printf depending on the length of the argv, where the argv[1] is the format string.

int main(int argc, char *argv[]) {
printf(argv[1], argv[2], ...);
}

Uses can call the program as ./prog "%s %s" a b, ./prog "%s %s %s" a b c, and so on.

Could anybody let me know how to achieve this in C?

Upvotes: 0

Views: 171

Answers (4)

mediocrevegetable1
mediocrevegetable1

Reputation: 4217

Here's something I just hacked together right now, it does does minimal parsing of the string and leaves most of it up to printf. It should also work with any number of arguments. Of course, since arguments are passed as char *s through the command line, this will only work with %s and its variants (and %%, but not sure if that counts as a format specifier).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        fprintf(stderr, "Usage: %s <format string>[ <args>]\n", argv[0]);
        return 1;
    }
    // These pointers will constantly jump from format spec. to format spec.
    char *last_fmt = argv[1], *next_fmt, *next_next_fmt;
    char *buf = NULL; // a buffer to hold a substring of argv[1]
    unsigned i = 2;       // Used to iterate over argv[2+]
    while (1)
    {
        next_fmt = last_fmt - 2;
        do
        {
            if ((next_fmt = strchr(next_fmt + 2, '%')) == NULL)
            {
                /* Your compiler may warn about this line specifically (it did for me),
                   but rest assured that there are indeed no format specifiers
                   here really, printf is just needed for printing "%%"s as "%"s */
                printf(last_fmt);
                return 0;
            }
        } while (next_fmt[1] == '%');
        next_next_fmt = next_fmt - 1;
        do
        {
            if ((next_next_fmt = strchr(next_next_fmt + 2, '%')) == NULL)
            {
                printf(last_fmt == argv[1] ? last_fmt : next_fmt,
                       argv[i]);
                return 0;
            }
        } while (next_next_fmt[1] == '%');
        buf = malloc(next_next_fmt - last_fmt + 1);
        memcpy(buf, last_fmt, next_next_fmt - last_fmt);
        buf[next_next_fmt - last_fmt] = '\0';
        printf(buf, argv[i]);
        free(buf);
        ++i;
        last_fmt = next_next_fmt;
    }
}

An example of running:

./a.out "Hello %.2s World! %s" "foo" "bar"
Hello fo World! bar

./a.out "Hello %10s World!" "foo" "bar"
Hello        foo World!

./a.out "Hello %5.2s World!" "random"
Hello    ra World!

./a.out
Usage: ./a.out <format string>[ <args>]

./a.out "Hello %%s World %s" "a"
Hello %s World a

./a.out "%s %s %s" "a" "b" "c"
a b c

You could build upon this yourself, but if you want to handle other format specifiers, you'll have to do actual parsing of the string. At that point, you would basically be creating another printf.

You also might be a bit worried about the use of a not-string-literal passed to printf, but this is safe. There is guaranteed to be exactly 1 format specifier in each place I use printf (except in the first do loop, but there it is guaranteed to not have any arguments).

Upvotes: 1

Lundin
Lundin

Reputation: 213940

This isn't a great idea to begin with, it will be super-vulnerable to all manner of exploits, typos and bugs. But if you insist, you could do a dirty hack as follows:

Assuming the format string in argv[1] is %s %s %s, then each we can divide this string length by 3 to get the number of strings. Save for the final one, which isn't followed by a trailing space. So strlen(argv[1]) + 1 then divide by 3:

#define STR_N ((strlen(argv[1])+1)/3)

Next up we can take advantage of printf ignoring trailing arguments not corresponding to the format string. So we could do printf(argv[1], argv[2], argv[3]); just fine without actually passing that many arguments, long as the format string contains the correct amount of conversion specifiers. For example:

#define ARGV_LIST \
argv[2],\
argv[3],\
argv[4],\
argv[5],\
argv[6],\
argv[7],\
argv[8],\
argv[9]\

printf(argv[1], ARGV_LIST);

Then cook up something to convert the indices and make sure that array out of bounds never occurs:

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

#define STR_N ((strlen(argv[1])+1)/3)
#define INDEX(n) (STR_N>n? (n+2) : 0)

#define ARGV_LIST \
argv[INDEX(0)],\
argv[INDEX(1)],\
argv[INDEX(2)],\
argv[INDEX(3)],\
argv[INDEX(4)],\
argv[INDEX(5)],\
argv[INDEX(6)],\
argv[INDEX(7)],\
argv[INDEX(8)],\
argv[INDEX(9)]\

int main(int argc, char *argv[]) 
{
  printf(argv[1], ARGV_LIST);
  return 0;   
}

Tested in Windows with prog.exe "%s %s %s %s %s" hello this is a test gives output:

hello this is a test

Upvotes: 0

KamilCuk
KamilCuk

Reputation: 141155

how to achieve this in C?

C language does not have reflection. You can't "dynamically create function calls" or inspect and then change your own source code. You have to know at compilation time how many arguments you are passing to a function. While it is simple to do printf("%s %s", "a", "b) inside C language, if you pass the same data to a program that was written in C language you have parse the data and write the logic yourself.

Such parser would take the string "%s %s" find the %s sequences and replace them for the string "a" and "b" and also print the space in between, etc. That parser has to be written in C and is basically a duplication of what printf program (not printf() C function) does. You may want to read some implementations of printf program: ex. coreutils printf.c or freebsd printf.c.

Upvotes: 0

dbush
dbush

Reputation: 224062

You need a loop for this:

int main(int argc, char *argv[]) 
{
    int i;
    for (i=1;i<argc;i++) {
      printf("%s", argv[i]);
    }
}

Upvotes: 2

Related Questions