Как получить имя файла из командной строки - PullRequest
1 голос
/ 31 марта 2019

Мне известно, что вопросы такого рода задавались ранее при переполнении стека, но я не смог добиться успеха даже после прочтения некоторых тем ( 1 ) ( 2 ).

Я пишу функцию C, которая будет читать имя файла, указанное в качестве аргумента командной строки.Но у меня также есть место для одного необязательного аргумента, который предшествует имени файла.

Пример выполнения (эти три вызова не зависят друг от друга):

./my_program -a foo.txt  // Standalone example #1
./my_program -b foo.txt  // Standalone example #2
./my_program foo.txt  // Standalone example #3

Myкод:

int main(int argc, char* argv[]) {

  int aflag = 0;
  int bflag = 0;
  int cflag = 0;
  int option;
  char *filename;

  while ((option = getopt(argc, argv, "abc:")) != -1) {
    switch (option) {
    case 'a':
      aflag = 1;
      break;
    case 'b':
      bflag = 1;
      break;
    case 'c':
      cflag = 1;
      break;
    default:
      aflag = 1;  // If no flags are set, use "a"
      break;
    }
  }

  if (argc == 2) {
    filename = argv[1];
  } else if (argc == 3) {
    filename = argv[2];
  }

  printf("Flags: aflag = %d, bflag = %d, cflag = %d\n", aflag, bflag, cflag);
  printf("Got filename = %s\n", filename);

Этот работает для случая с одним необязательным аргументом.

Однако я читал о [optind] ( 3 ) и мне было интересно, как правильно его использовать, чтобы я мог получить имя файла.Кажется, я не могу заставить его работать, и я не знаю, является ли использование if операторов вроде этого хорошим стилем.

Например, сейчас этот код ограничен one необязательный аргумент.Но что, если я позже решу добавить второй аргумент?Тогда мой приведенный выше код не будет работать, так как индекс argv, в котором находится имя файла, изменится.

Есть ли способ - предположительно с использованием getind и getopt - всегда захватыватьпоследний аргумент в качестве имени файла, независимо от того, сколько (необязательных) аргументов я указываю перед ним?

Ответы [ 2 ]

2 голосов
/ 31 марта 2019

со страницы, на которую вы ссылаетесь:

Если больше нет опционных символов, getopt () возвращает -1. Тогда optind - это индекс в argv первого argv-элемента, который не является опцией.

Так что вместо

if (argc == 2) {
    filename = argv[1];
  } else if (argc == 3) {
    filename = argv[2];
  }

Вы просто хотите

filename = argv[optind];

Обратите внимание, что если после опций не было задано ни одного аргумента (например, если ваша программа была вызвана просто ./my_program -a, тогда для filename будет установлено значение NULL, и вы должны быть готовы обработать это соответствующим образом. Вы можете также определите этот случай явно, если вы хотите:

if (optind < argc) {
    filename = argv[optind];
} else {
    fprintf(stderr, "Usage: %s -a|-b|-c filename\n", argv[0]);
    exit(2);
}
1 голос
/ 31 марта 2019

Когда вы дружите с getopt, помните, что getopt в основном анализирует вашу командную строку, сопоставляет параметры и любые аргументы с параметрами, требующими ввода значения.Все остальные неопционные аргументы переупорядочиваются, поэтому они появляются в конце списка аргументов.Когда вы выполняете цикл, как при обычной проверке, например, while ((opt = getopt (argc, argv, "f:ohv")) != -1), любые аргументы командной строки, которые не были опциями и не являлись обязательными значениями для опции, будут начинаться с argv[optind].Поэтому, когда ваш цикл обработки аргументов завершен, вы проверяете if (optind < argc), чтобы определить, есть ли у вас дополнительные доступные аргументы командной строки, которые не были обработаны в вашем цикле getopt.

Давайте рассмотрим достаточно полный пример обработки имени файлалибо указывается после параметра "-f", либо просто как первый параметр без аргументов, который остается после обработки всех параметров (или мы будем читать stdin, если не останется никаких дополнительных параметров - но обратите внимание, в этом случае вы не можетеиметь дополнительные опции, или первая будет считаться именем файла для чтения)

Один из самых простых / удобных способов обработки аргументов - просто объявить массив опций, который вы инициализируете, как все нули.Затем при обработке опций вы используете массив opts, где каждый элемент содержит индекс соответствующей опции в argv, либо флаг (например, установлен на 1, если опция установлена), либо значение, полученное изпреобразование (скажем, если у вас было "-n:" для ввода некоторого числа, то с помощью командной строки, содержащей "-n 4", вы можете преобразовать и сохранить фактическое значение 4 в индексе массива, связанном с параметром "-n" (вместоиндекс argv, который вы должны были бы преобразовать в числовое значение позже)).

Целью вашей функции processopts() является цикл с getopt() и полное превращение любых параметров в пригодные для использования значения для остальной части вашей программы.Используя массив опций, это облегчает передачу в качестве параметра функции для обработки всех ваших опций.Сделав тип массива опций long, вы получите доступную собственную ширину для и strtol преобразований, а также сможете обрабатывать как положительные, так и отрицательные значения.

Итак, давайте рассмотрим примериспользуя функцию processopts()main() или везде, где вы будете вызывать processopts(), вы просто объявляете массив, в котором каждый элемент будет соответствовать некоторой опции, которую вы будете обрабатывать, и будете содержать значимое значение после обработки этой опции, например,

#define NOPTS   8   /* max options for sizing opts array */
...
int main (int argc, char **argv) {

    long opts[NOPTS] = {0};  /* initialize opitons array all zero */
    ...
    int optindex = processopts (argc, argv, opts);  /* process all options */

Итак, выше вы объявили массив opts и передали его вместе с argc, и argv в функцию processopts().Ваша функция processopts() будет тогда делать что-то похожее на:

/** process command line options with getopt.
 *  values are made available through the 'opts' array.
 *  'optind' is returned for further command line processing.
 */
int processopts (int argc, char **argv, long *opts)
{
    int opt;

    /* set any default values in *opts array here */

    while ((opt = getopt (argc, argv, "f:ohv")) != -1) {  /* getopt loop */
        switch (opt) {
            case 'f':       /* filename */
                opts[0] = optind - 1;
                break;
            case 'o':       /* some generic option 'o' */
                opts[1] = 1;
                break;
            case 'h':       /* help */
                help (EXIT_SUCCESS);
            case 'v':       /* show version information */
                printf ("%s, version %s\n", PACKAGE, VERSION);
                exit (EXIT_SUCCESS);
            default :       /* ? */
                fprintf (stderr, "\nerror: invalid or missing option.\n");
                help (EXIT_FAILURE);
        }
    }
    /* set argv index for filename if arguments remain */
    if (!opts[0] && argc > optind) opts[0] = optind++;

    return optind;  /* return next argument index */
}

Примечание выше, если задана опция "-f filename" opts[0] устанавливается в индекс следующего аргумента (имя файла), а затем тест в конце, чтобы определить, нужно ли проверять дополнительные аргументы для использования, так как имя файла пропускается, поскольку opts[0] больше не 0.Но если opts[0] не было установлено, index для первого неопционального аргумента будет сохранено в opts[0].Независимо от того, было ли имя файла взято после "-f" или считано в качестве первого неопционального аргумента, вы должны затем вызвать fopen (argv[opts[0]], "r"), чтобы открыть файл в main().

Также обратите внимание на , чтоВозвращается optind, позволяя вам определить, были ли дополнительные (или дополнительные) аргументы, которые не были обработаны в вашем цикле getopt, так что вы можете проверить if (optind < argc) обратно в main() и обработать дополнительные аргументы, как считаете нужным.

Собрав его вместе в коротком (для getopt) примере, вы можете поэкспериментировать с чем-то вроде следующего, чтобы передать имя файла после "-f" или где-либо еще без "-f", пока он не является первым.оставшийся аргумент, например

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* for getopt */

#define PACKAGE "getopt_example"
#define VERSION "0.01"

#define NOPTS   8   /* max options for sizing opts array */
#define MAXC 1024   /* max characters for buffer */

int processopts (int argc, char **argv, long *opts);
void help (int xcode);
size_t rmcrlf (char *s);

int main (int argc, char **argv) {

    long opts[NOPTS] = {0};  /* initialize opitons array all zero */
    char buf[MAXC] = "";
    size_t idx = 0;
    int optindex = processopts (argc, argv, opts);

    /* use filename provided as following "-f" option or provided as
     * 1st non-option argument (stdin by default)
     */
    FILE *fp = opts[0] ? fopen (argv[opts[0]], "r") : stdin;
    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }
    /* indicate whether the option '-o' was set */
    printf ("\nthe option '-o' %s set.\n\n", opts[1] ? "is" : "is not");

    printf (" line : len - contents\n\n");
    while (fgets (buf, MAXC, fp)) { /* read ouput length/lines from file */
        size_t l = rmcrlf (buf);    /* get line length, trim line ending */
        printf (" %4zu : %3zu - %s\n", idx++, l, buf);
    }

    if (fp != stdin)        /* close file if not stdin */
        fclose (fp);

    if (optindex < argc)    /* check whether additional options remain */
        printf ("\nwarning: %d options unprocessed.\n\n", argc - optindex);

    for (int i = optindex; i < argc; i++)   /* output unprocessed options */
        printf (" %s\n", argv[i]);

    return 0;
}

/** process command line options with getopt.
 *  values are made available through the 'opts' array.
 *  'optind' is returned for further command line processing.
 */
int processopts (int argc, char **argv, long *opts)
{
    int opt;

    /* set any default values in *opts array here */

    while ((opt = getopt (argc, argv, "f:ohv")) != -1) {
        switch (opt) {
            case 'f':       /* filename */
                opts[0] = optind - 1;
                break;
            case 'o':       /* some generic option 'o' */
                opts[1] = 1;
                break;
            case 'h':       /* help */
                help (EXIT_SUCCESS);
            case 'v':       /* show version information */
                printf ("%s, version %s\n", PACKAGE, VERSION);
                exit (EXIT_SUCCESS);
            default :       /* ? */
                fprintf (stderr, "\nerror: invalid or missing option.\n");
                help (EXIT_FAILURE);
        }
    }
    /* set argv index for filename if arguments remain */
    if (!opts[0] && argc > optind) opts[0] = optind++;

    return optind;  /* return next argument index */
}

/** display help */
void help (int xcode)
{
    xcode = xcode ? xcode : 0;

    printf ("\n %s, version %s\n\n"
            "  usage:  %s [-hv -f file (stdin)] [file]\n\n"
            "  Reads each line from file, and writes line, length and contents\n"
            "  to stdout.\n\n"
            "    Options:\n\n"
            "      -f file    specifies filename to read.\n"
            "                 (note: file can be specified with or without -f option)\n"
            "      -o         generic option for example.\n"
            "      -h         display this help.\n"
            "      -v         display version information.\n\n",
            PACKAGE, VERSION, PACKAGE);

    exit (xcode);
}

/** remove newline or carriage-return from 's'.
 *  returns new length on success, -1 of 's' is NULL.
 */
size_t rmcrlf (char *s)
{
    size_t len;

    if (!s) return 0;                       /* validate s not NULL */

    s[(len = strcspn (s, "\r\n"))] = 0;     /* nul-terminate saving len */

    return len;     /* return len */
}

(программа сообщит вам, если установлена ​​опция "-o" "is" или "is not", а затем просто прочитает имя файла, найденное в аргументах командной строки (или stdin, если не указано имя файла или дополнительный аргумент)и выплевывает индекс строки (0 - N-1), длину строки и, наконец, саму строку, за которой следуют любые дополнительные аргументы, не обработанные getopt или функцией processopts().

ПримерКомандная строка может быть:

$ ./bin/getopt_min -f dat/captnjack.txt extra1 extra2

(прочитать файл dat/captnjack.txt и показать, что два дополнительных аргумента не обработаны)

$ ./bin/getopt_min dat/captnjack.txt -o extra1 extra2

(то же самое)

$ ./bin/getopt_min -o <dat/captnjack.txt

(файл читается на stdin)

Наконец, опции "-h" и "-v" просто приводят к отображению справки или информации о версии.

Просмотрите все и дайте мне знать, еслиу вас есть вопросы. Требуется некоторое время, чтобы переварить getopt, это нормально, просто держите страницу руководства открытой и проработайте пару примеров.

...