getopt с повторяющимися и необязательными аргументами

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

Это должно работать как mk_part -s size [-s size ...] [name].

[name] — это имя файла на диске, оно необязательное, потому что есть имя по умолчанию.

Я не очень хорошо разбираюсь в getopt_long (и getopt), но все, что я прочитал, это то, что я получаю параметры через какое-то время, поэтому два способа обработки для меня будут такими:

  1. сохраните все размеры в массиве, а затем запишите их в таблицу.
  2. писать размер прямо во время парсинга

Для первого выбора сложность в том, что я не знаю количество разделов. Но я все еще мог бы увеличить это число с помощью argc или лучше с помощью (argc-1)/2, и это сработало бы.

Для второго варианта я не знаю, какой файл писать.

Итак, как лучше всего получить все эти аргументы и как я могу получить это дополнительное имя?


person user3593232    schedule 09.12.2015    source источник


Ответы (2)


getopt прекрасно справляется как с повторяющимися, так и с необязательными аргументами. Для повторяющихся аргументов каждый вызов getopt даст вам следующий аргумент. getopt не волнует, что это повторяется. Что касается аргумента в конце, просто нужно проверить его наличие после анализа всех параметров. Ниже приведен код, измененный из примера на справочной странице getopt для обработки вашего сценария:

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

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

    while ((opt = getopt(argc, argv, "s:")) != -1) {
        switch (opt) {
        case 's':
            printf("size=%d\n", atoi(optarg));
            break;
        default: /* '?' */
            exit(EXIT_FAILURE);
        }
    }

    if (optind < argc) {
        printf("name=%s\n", argv[optind]);
    } else {
        printf("optional name arg not present\n");
    }

    exit(EXIT_SUCCESS);
}

А вот несколько примеров запуска программы, показывающих, как она обрабатывает повторяющиеся параметры и аргумент в конце.

$ ./a.out -s 10 -s 20 -s 30
size=10
size=20
size=30
optional name arg not present

$ ./a.out -s 1 my_name
size=1
name=my_name
person kaylum    schedule 09.12.2015
comment
Спасибо, но на самом деле сложность заключается в том, чтобы получить все размеры перед записью в файл optionnal/default, поэтому речь идет о сохранении optarg каждого -s. - person user3593232; 10.12.2015
comment
@user3593232 user3593232 Я тебя не понимаю. Приведенный выше код позволит вам проанализировать все размеры. Код записи файла будет идти после этого. Конечно, приведенный выше код является всего лишь образцом и выводит только размеры. Ваш реальный код сохранит проанализированные размеры в массив, список или какую-либо другую структуру. То есть замените приведенную выше строку printf("size=%d\n", atoi(optarg)); кодом, в котором хранится то же самое значение optarg. - person kaylum; 10.12.2015
comment
проблема в том, что я не знаю размер массива. Но это не проблема, так как я могу решить это с помощью argc... Я признаю, что это не очень хороший вопрос. Что меня смущало поначалу, так это то, что я объявлю массив, который не будет выполняться. В моем случае это не имеет большого значения, но в случае, если есть много возможных различных повторяющихся опций, может быть большой потерей объявление массива argc для каждого из них. Я могу быть немного параноиком! - person user3593232; 10.12.2015

Я думаю, вы слишком много думаете об этом. Я знаю, что всегда заманчиво попытаться избежать malloc, но эффективность синтаксического анализа параметров никогда (*) не важна. Вы анализируете параметры только один раз, и, учитывая стоимость инициализации нового процесса, поиска исполняемого файла, его связывания и загрузки и всего остального процесса запуска команды, время, необходимое для анализа параметров, вероятно, даже не шум. .

Так что просто сделайте это самым простым способом. Вот один из возможных набросков:

int main(int argc, char* argv) {
  /* These variables describe the options */
  int  nparts = 0;              // Number of partitions
  unsigned long* parts = NULL;  // Array of partitions (of size nparts)
  const char* diskname="/the/default/name"; // Disk's filename

  for (;;) {
    switch (getopt(argc, argv, "s:")) {
      case '?':
        /* Print usage message */
        exit(1);
      case 's':
        /* Some error checking missing */
        parts = realloc(parts, ++nparts * sizeof *parts);
        parts[nparts - 1] = strtoul(optarg, NULL, 0);
        continue;
      case -1:
        break;
    }
    break;
  }
  if (optind < argc) diskname = argv[optind++];
  if (optind != argc) {
    /* print error message */ 
    exit(1);
  }
  return do_partitions(diskname, parts, nparts);
}

В приведенном выше коде отсутствует множество проверок ошибок и других тонкостей, но он краток и по существу. Он просто перераспределяет массив разделов каждый раз, когда обнаруживается новый размер. (Вероятно, это не так ужасно, как вы думаете, потому что сам realloc, вероятно, достаточно умен, чтобы экспоненциально увеличить размер выделения. Но даже если бы это было ужасно, это не будет происходить достаточно часто, чтобы даже заметить.)

Трюк с continue и break — это распространенный способ вложения переключателя внутрь for. В переключателе continue продолжит цикл for, а break выйдет из переключателя; поскольку все действия переключения, которые не завершают цикл for continue, все, что следует за блоком переключения, выполняется только для действия переключения, которое явно breaks. Таким образом, break, следующий за блоком switch, прерывает цикл for именно в тех случаях, когда действие switch выполнило break.

Возможно, вы захотите проверить, что был определен хотя бы один размер раздела, прежде чем вызывать функцию, которая выполняет переразметку.

person rici    schedule 10.12.2015