0

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?

3
  • 2
    github.com/freebsd/freebsd-src/blob/main/usr.bin/printf/… (BSD source for the standard printf command line utility which does what you're being asked to do.) You cannot fabricate an argument list to printf, so you have to parse the format string yourself. The linked code is an example. Commented May 7, 2021 at 4:14
  • could do a bit of the old macro preprocessing Commented May 7, 2021 at 5:09
  • How do you intend to deal with non-string format strings? Commented May 7, 2021 at 7:11

4 Answers 4

2

You need a loop for this:

int main(int argc, char *argv[]) 
{
    int i;
    for (i=1;i<argc;i++) {
      printf("%s", argv[i]);
    }
}
Sign up to request clarification or add additional context in comments.

2 Comments

No. argv[1] is a format string that governs all the rest of argv[2] .... Uses can call the program as ./prog "%s %s" a b, ./prog "%s %s %s" a b c, and so on.
@user1424739 In that case, you'll probably need to parse the format string yourself.
1

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).

9 Comments

it will explode for ./a.out "Hello %%s World %s" "a"
@tstanisl hmm, you're right. That seems a bit tough to fix based on the way my program currently works, but I'll at least try working out the %% issue :/
@tstanisl with the help of an inner do ... while loop, the %% issue has been fixed (I think). Thanks.
Still fails for /a.out "%s%%" "a" producing as% rather than a%
@tstanisl just had to replicate the loop for next_next_fmt too, works now.
|
0

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.

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.