ccommand-linecommand-line-argumentsgetopt

Parsing Multiple Arguments with getopt()


I am working on a simple decimal to hex/binary calculator that will take command line arguments to specify which to convert to, -x for hex and -b for binary. I am using getopt() to parse in values.

I am having a couple issues namely how to work with multiple arguments after one option. My code (as of now) is as follows

int main(int argc, char *argv[]){  
    int opt;
    int num;
    while((opt = getopt(argc, argv, ":x:b:")) != -1){
        switch(opt){
            case 'x':
                num = atoi(optarg);
                dec_to_hex(num);
                break;
            case 'b':
                num = atoi(optarg);
                dec_to_bin(num);
                break;
            case'?':
                print_usage();
                break;
            case ':':
                print_usage();
                break;
            default:
                print_usage();
        }

    }
    return 0;
}

For example, if I call ./a.out -b 1234 4321 I want to call my dec_to_bin fucntion first one 1234, then on 4321. I would also like to print usage if no/incorrect options are given. If I call ./a.out 1234 or ./a.out -x -p 1234 it should print usage.

I have tried working with a while loop in the switch statement cases like this,

case 'x':
    while(optind < argc){
        num = atoi(argv[optind++]);
        dec_to_hex(num);
    }

But that still will only print the first conversion. I also thought the return of '?', ':' or defualt would cover my incorrect/no options given. I am pretty stumped and would love any help. Thanks!


Solution

  • Options conventionally only take zero or one arguments. For your code, you really want the option to set the mode in which your program operates; for that you don't need arguments to the options. After deciding on the operating mode, you can iterate over all the non-option arguments, performing the appropriate conversion.

    Something like this:

    #include <stdlib.h>
    #include <stdio.h>
    #include <getopt.h>
    
    #define CONV_DEC_TO_HEX 0
    #define CONV_DEC_TO_BIN 1
    
    char *prog_name;
    
    void dec_to_hex(int num) {
        printf("0x%x\n", num);
    }
    
    void dec_to_bin(int num) {
        printf("0b%b\n", num);
    }
    
    void print_usage(FILE *out) {
        fprintf(out, "usage: %s [-xb] arg1 [...argn]\n", prog_name);
    }
    
    int main(int argc, char *argv[]){  
        int opt;
        int num;
        int conv = -1;
    
        prog_name = argv[0];
    
        while((opt = getopt(argc, argv, "hxb")) != -1){
            switch(opt){
                case 'x':
                    conv = CONV_DEC_TO_HEX;
                    break;
                case 'b':
                    conv = CONV_DEC_TO_BIN;
                    break;
                case 'h':
                    print_usage(stdout);
                    exit(0);
                default:
                    print_usage(stderr);
                    exit(2);
            }
        }
    
        if (conv == -1) {
            fprintf(stderr, "ERROR: please select one of -x or -b\n");
            print_usage(stderr);
            exit(2);
        }
    
        for (int i=optind; i<argc; i++) {
            int arg = atoi(argv[i]);
            switch(conv) {
                case CONV_DEC_TO_HEX:
                    dec_to_hex(arg);
                    break;
                case CONV_DEC_TO_BIN:
                    dec_to_bin(arg);
                    break;
            }
        }
    
        return 0;
    }
    

    With no arguments:

    $ ./calc
    ERROR: please select one of -x or -b
    usage: ./calc [-xb] arg1 [...argn]
    

    With invalid arguments:

    $ ./calc -z
    ./calc: invalid option -- 'z'
    usage: ./calc [-xb] arg1 [...argn]
    

    With valid arguments:

    $ ./calc -x 123 234 345
    0x7b
    0xea
    0x159
    $ ./calc -b 123 234 345
    0b1111011
    0b11101010
    0b101011001
    

    And like all good programs, asking for help (with ./calc -h) outputs to stdout instead of stderr.