24

I'm making a small program in C that deals with a lot of command line arguments, so I decided to use getopt to sort them for me.

However, I want two non-option arguments (source and destination files) to be mandatory, so you have to have them as arguments while calling the program, even if there's no flags or other arguments.

Here's a simplified version of what I have to handle the arguments with flags:

while ((c = getopt(argc, argv, "i:d:btw:h:s:")) != -1) {
    switch (c) {
        case 'i': {
            i = (int)atol(optarg);
        }
        case 'd': {
            d = (int)atol(optarg);
        }
        case 'b':
            buf = 1;
            break;
        case 't':
            time = 1;
            break;
        case 'w':
            w = (int)atol(optarg);
            break;
        case 'h':
            h = (int)atol(optarg);
            break;
        case 's':
            s = (int)atol(optarg);
            break;
        default:
            break;
    }
}

How do I edit this so that non-option arguments are also handled?

I also want to be able to have the non-options either before or after the options, so how would that be handled?

Conor Taylor
  • 2,998
  • 7
  • 37
  • 69

6 Answers6

39

getopt sets the optind variable to indicate the position of the next argument.

Add code similar to this after the options loop:

if (argv[optind] == NULL || argv[optind + 1] == NULL) {
  printf("Mandatory argument(s) missing\n");
  exit(1);
}

Edit:

If you want to allow options after regular arguments you can do something similar to this:

while (optind < argc) {
  if ((c = getopt(argc, argv, "i:d:btw:h:s:")) != -1) {
    // Option argument
    switch (c) {
        case 'i': {
            i = (int)atol(optarg);
        }
        case 'd': {
            d = (int)atol(optarg);
        }
        case 'b':
            buf = 1;
            break;
        case 't':
            time = 1;
            break;
        case 'w':
            w = (int)atol(optarg);
            break;
        case 'h':
            h = (int)atol(optarg);
            break;
        case 's':
            s = (int)atol(optarg);
            break;
        default:
            break;
    }
    else {
        // Regular argument
        <code to handle the argument>
        optind++;  // Skip to the next argument
    }
}
Klas Lindbäck
  • 33,105
  • 5
  • 57
  • 82
  • 1
    OK, but my loop will exit if the mandatory arguments come before the optional ones, so only the mandatory ones will be processed and not the optional ones. How do i fix this? – Conor Taylor Aug 07 '13 at 11:38
  • 1
    It is common to require that options come before arguments. Just specify it in the `man` page. – Klas Lindbäck Aug 07 '13 at 12:52
  • 2
    Yeah I know but for example with the ssh command, the -p flag can come either before *or* after the mandatory username@server argument. I just wanna know how to do that – Conor Taylor Aug 07 '13 at 13:26
  • @Conor I've added an outline on how to do it, see the edit above. – Klas Lindbäck Aug 07 '13 at 13:41
  • Your edit code was very helpful, thanks. A little silly that I have to force continued parsing by micro-managing a global variable, but it gets the job done! – Evan Senter Aug 07 '14 at 20:09
  • Should check that `argc > optind` before accessing `argv[optind]`! – gatopeich Jul 25 '17 at 11:56
  • 2
    @gatopeich `argv[argc]` is guaranteed by the C standard to be `NULL`, so the code I provided should work just fine. – Klas Lindbäck Jul 26 '17 at 08:32
  • Thanks, I did not know that. Still it is strictly safer and more efficient to compare `argc` with `optind`: `if ( optind+2 > argc) { }`. – gatopeich Jul 27 '17 at 10:51
  • 5
    The example you provided under EDIT clearly doesn't work (at least on Linux), I can't believe nobody has noticed that since this was first posted. – Peter Jul 07 '19 at 12:46
  • Is this a ChatGPT answer? The `else` clause follows `switch` *(not an `if`)*, the indentation is screwed up because the closing parentheses of `if` is aligned to where `while` closing parentheses supposed to be; the `while` closing parentheses is absent completely; and on top of that, the code in `EDIT` couldn't possibly work even if fixed *(and I tested that)*, because [per `getopt` documentation](https://man7.org/linux/man-pages/man3/getopt.3.html#RETURN_VALUE) `-1` is only returned if *everything* has been parsed, which is unrelated to the presence of dash inside an arg. – Hi-Angel Mar 06 '23 at 15:33
17

Really good example could be found here: GNU Libc The code:

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main (int argc, char **argv)
{
int aflag = 0;
int bflag = 0;
char *cvalue = NULL;
int index;
int c;

opterr = 0;

while ((c = getopt (argc, argv, "abc:")) != -1)
switch (c)
{
case 'a':
    aflag = 1;
    break;
case 'b':
    bflag = 1;
    break;
case 'c':
    cvalue = optarg;
    break;
case '?':
    if (optopt == 'c')
    fprintf (stderr, "Option -%c requires an argument.\n", optopt);
    else if (isprint (optopt))
    fprintf (stderr, "Unknown option `-%c'.\n", optopt);
    else
    fprintf (stderr,
        "Unknown option character `\\x%x'.\n",
        optopt);
    return 1;
default:
    abort ();
}

printf ("aflag = %d, bflag = %d, cvalue = %s\n",
    aflag, bflag, cvalue);

for (index = optind; index < argc; index++)
printf ("Non-option argument %s\n", argv[index]);
return 0;
}

It allows to have options before and after arguments. I did compile and run test example:

$ ./a.out aa ff bb -a -ctestparam hello
aflag = 1, bflag = 0, cvalue = testparam
Non-option argument aa
Non-option argument ff
Non-option argument bb
Non-option argument hello
ulidtko
  • 14,740
  • 10
  • 56
  • 88
Madars Vi
  • 947
  • 9
  • 12
3

According to https://www.man7.org/linux/man-pages/man3/getopt.3.html

By default, getopt() permutes the contents of argv as it scans, so that eventually all the nonoptions are at the end. Two other scanning modes are also implemented. If the first character of optstring is '+' or the environment variable POSIXLY_CORRECT is set, then option processing stops as soon as a nonoption argument is encountered. If the first character of optstring is '-', then each nonoption argv-element is handled as if it were the argument of an option with character code 1. (This is used by programs that were written to expect options and other argv-elements in any order and that care about the ordering of the two.) The special argument "--" forces an end of option-scanning regardless of the scanning mode.

  • While your citation may inform about the issue at hand, pls. elaborate on your answer to address the question "How do I edit this so that non-option arguments are also handled?" – code-kobold Jan 24 '21 at 17:15
1
int main(int argc, char** argv) {
    
    
  char* inputfile;
  char* outputfile;
  char* output_file_type;
  char* color_red;
  char* color_blue;
  char* color_green;
  
  
  int opt;
  
  if (argv[optind] == NULL || argv[optind + 1] == NULL) {
  printf("Mandatory argument(s) missing\n");
  exit(1);
    }
 
  
  while((opt = getopt(argc, argv, ":i:o:r:g:b:t:")) != -1){
    switch(opt){
      case 'i':
        inputfile = optarg;
        printf("Input file : %s\n",inputfile);
        break;  
      case 'o':
        outputfile = optarg;
        printf("Output File: %s\n",outputfile);
        break;
      case 't':
        output_file_type = optarg;
        printf("Output File type: %s\n", output_file_type);
        break;
      case 'r':
        color_red = optarg;
        printf("Color Red: %s\n",color_red);
        break;
      case 'g':
        color_green = optarg;
        printf("Color Green: %s\n",color_green);
        break;
      case 'b':
        color_blue = optarg;
        printf("Color Blue: %s\n",color_blue);
        break;
      case ':':
        printf("option needs a value\n");
        break;
      case '?':
        printf("unknown option: %c\n", optopt);
        break;
    }
  
  }
  
  for (; optind < argc; optind++){
      
      
       printf("Given extra arguments: %s\n", argv[optind]);
     
   
  }  


    return (EXIT_SUCCESS);
}

Run commands:

gcc main.c -o image
./image -i ./resource/input_file.bmp -o ./resource/output_file.bmp -t BPM -r 10 -g 24 -b 40

output:

Input file : ./resource/input_file.bmp
Output File: ./resource/output_file.bmp
Output File type: BPM
Color Red: 10
Color Green: 24
Arpan Saini
  • 4,623
  • 1
  • 42
  • 50
0

The author of Mead's Guide to getopt states

If you want to have getopt parse and return the non-option arguments in the while loop (in the order specified), you must direct it to do so by putting a minus (-) in front of the optstring.

The example provided was "-:a:b:X" where the minus (-) "disables getopt from moving all non-option arguments to the end of the command line" and the colon (:) "disables getopt from displaying error messages".

If a non-option argument is found, then getopt will return an integer value of 1.

Dada
  • 6,313
  • 7
  • 24
  • 43
Cheena
  • 1
  • 2
0

I'm going to go ahead and throw my answer in. I've tested it, and it complies with getopt as described here on Linux. Note, this answer only allows options first, then non-options after. But this is normal for a lot of CLI tools.

I'm adding this answer now because I found the EDIT in the answer from Klas did not work.


#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <ctype.h>


int main (int argc, char * const argv[])
{
    int c;
    
    // Process option arguments
    while (-1 != (c = getopt(argc, argv, "abc:"))) {
         switch (c) {
     case 'a': printf("option: a\n"); break;
     case 'b': printf("option: b\n"); break;
     case 'c': printf("option: c with arg: \"%s\"\n", optarg); break;
     default:
         printf("unknown arg: %2X\n", optopt);
     }
    }
    
    // Process remaining arguments
    for (int i = optind; i < argc; ++i) {
        printf("non-option: %s\n", argv[i]);
    }
    
    return EXIT_SUCCESS;
}
Micrified
  • 3,338
  • 4
  • 33
  • 59