Analizowanie argumentów wiersza poleceń w C?

98

Próbuję napisać program, który może porównywać dwa pliki wiersz po wierszu, słowo po słowie lub znak po znaku w C. Musi być w stanie czytać w opcjach wiersza poleceń -l -w -i or --...

  • jeśli opcją jest -l, porównuje pliki linia po linii.
  • jeśli opcją jest -w, porównuje pliki słowo po słowie.
  • jeśli opcje są - automatycznie zakłada, że ​​następny argument jest pierwszą nazwą pliku.
  • jeśli opcją jest -i, porównuje je bez uwzględniania wielkości liter.
  • domyślnie porównuje pliki znak po znaku.

Nie powinno mieć znaczenia, ile razy opcje są wprowadzane, o ile -w i -l nie są wprowadzane w tym samym czasie i nie ma więcej lub mniej niż 2 plików.

Nie wiem nawet, od czego zacząć analizowanie argumentów wiersza poleceń. PROSZĘ POMÓŻ :(

Więc to jest kod, który wymyśliłem na wszystko. Nie sprawdziłem jeszcze błędów, ale zastanawiałem się, czy piszę w zbyt skomplikowany sposób?

/*
 * Functions to compare files.
 */
int compare_line();
int compare_word();
int compare_char();
int case_insens();

/*
 * Program to compare the information in two files and print message saying 
 * whether or not this was successful.
 */
int main(int argc, char* argv[])
{
/*Loop counter*/
  size_t i = 0;

  /*Variables for functions*/
  int caseIns = 0;
  int line = 0;
  int word = 0;

  /*File pointers*/
  FILE *fp1, *fp2;

  /*
   * Read through command-line arguments for options.
   */
  for (i = 1; i < argc; i++) {
    printf("argv[%u] = %s\n", i, argv[i]);
    if (argv[i][0] == '-') {
       if (argv[i][1] == 'i') 
       {
           caseIns = 1;
       }
       if (argv[i][1] == 'l')
       {
           line = 1;
       }
       if (argv[i][1] == 'w')
       {
           word = 1;
       }
       if (argv[i][1] == '-')
       {
           fp1 = argv[i][2];
           fp2 = argv[i][3];
       }
       else 
       {
           printf("Invalid option.");
           return 2;
       }
    } else {
       fp1(argv[i]);
       fp2(argv[i][1]);
    }
  }

  /*
   * Check that files can be opened.
   */
  if(((fp1 = fopen(fp1, "rb")) ==  NULL) || ((fp2 = fopen(fp2, "rb")) == NULL))
  {
      perror("fopen()");
      return 3;
  }
  else{
        if (caseIns == 1)
        {
            if(line == 1 && word == 1)
            {
                printf("That is invalid.");
                return 2;
            }
            if(line == 1 && word == 0)
            {
                if(compare_line(case_insens(fp1, fp2)) == 0)
                        return 0;
            }
            if(line == 0 && word == 1)
            {
                if(compare_word(case_insens(fp1, fp2)) == 0)
                    return 0;
            }
            else
            {
                if(compare_char(case_insens(fp1,fp2)) == 0)
                    return 0;
            }
        }
        else
        {
            if(line == 1 && word == 1)
            {
                printf("That is invalid.");
                return 2;
            }
            if(line == 1 && word == 0)
            {
                if(compare_line(fp1, fp2) == 0)
                    return 0;
            }
            if(line == 0 && word == 1)
            {
                if(compare_word(fp1, fp2) == 0)
                    return 0;
            }
            else
            {
                if(compare_char(fp1, fp2) == 0)
                    return 0;
            }
        }

  }
    return 1;
    if(((fp1 = fclose(fp1)) == NULL) || (((fp2 = fclose(fp2)) == NULL)))
        {
            perror("fclose()");
            return 3;
        }
        else
        {
            fp1 = fclose(fp1);
            fp2 = fclose(fp2);
        }
}

/*
 * Function to compare two files line-by-line.
 */
int compare_line(FILE *fp1, FILE *fp2)
{
    /*Buffer variables to store the lines in the file*/
    char buff1 [LINESIZE];
    char buff2 [LINESIZE];

    /*Check that neither is the end of file*/
    while((!feof(fp1)) && (!feof(fp2)))
    {
        /*Go through files line by line*/
        fgets(buff1, LINESIZE, fp1);
        fgets(buff2, LINESIZE, fp2);
    }
    /*Compare files line by line*/
    if(strcmp(buff1, buff2) == 0)
    {
        printf("Files are equal.\n");
        return 0;
    }
    printf("Files are not equal.\n");
    return 1;
}   

/*
 * Function to compare two files word-by-word.
 */
int compare_word(FILE *fp1, FILE *fp2)
{
    /*File pointers*/
    FILE *fp1, *fp2;

    /*Arrays to store words*/
    char fp1words[LINESIZE];
    char fp2words[LINESIZE];

    if(strtok(fp1, " ") == NULL || strtok(fp2, " ") == NULL)
    {
        printf("File is empty. Cannot compare.\n");
        return 0;
    }
    else
    {
        fp1words = strtok(fp1, " ");
        fp2words = strtok(fp2, " ");

        if(fp1words == fp2words)
        {
            fputs(fp1words);
            fputs(fp2words);
            printf("Files are equal.\n");
            return 0;
        }
    }
    return 1;
}

/*
 * Function to compare two files character by character.
 */
int compare_char(FILE *fp1,FILE *fp2)
{
    /*Variables to store the characters from both files*/
    int c;
    int d;

    /*Buffer variables to store chars*/
    char buff1 [LINESIZE];
    char buff2 [LINESIZE];

    while(((c = fgetc(fp1))!= EOF) && (((d = fgetc(fp2))!=EOF)))
    {
        if(c == d)
        {
            if((fscanf(fp1, "%c", buff1)) == (fscanf(fp2, "%c", buff2)))
            {
                printf("Files have equivalent characters.\n");
                return 1;
                break;
            }
        }

    }
        return 0;
}

/*
 * Function to compare two files in a case-insensitive manner.
 */
int case_insens(FILE *fp1, FILE *fp2, size_t n)
{
    /*Pointers for files.*/
    FILE *fp1, *fp2;

    /*Variable to go through files.*/
    size_t i = 0;

    /*Arrays to store file information.*/
    char fp1store[LINESIZE];
    char fp2store[LINESIZE];

    while(!feof(fp1) && !feof(fp2))
    {
         for(i = 0; i < n; i++)
         {
                fscanf(fp1, "%s", fp1store);
                fscanf(fp2, "%s", fp2store);

                fp1store = tolower(fp1store);
                fp2store = tolower(fp2store);

                return 1;
         }
    }
    return 0;
}
user1251020
źródło
Nie jestem do końca pewien, jak używać getopt () ... Jeszcze się o tym nie dowiedziałem na moich zajęciach.
user1251020
3
Więc idź i przeczytaj odpowiednią stronę podręcznika; nie jest to zbyt skomplikowane, a strona podręcznika prawdopodobnie zawiera przykład do eksperymentowania (a jeśli lokalna strona podręcznika tego nie robi, z pewnością możesz znaleźć przykłady w sieci).
Jonathan Leffler
1
To jest biblioteka wysokiego poziomu: argparse w c, bardzo łatwa w użyciu.
Cofyc
W przypadku prostych rzeczy możesz po prostu rzucić własne, zamiast korzystać z biblioteki. Napisałem tutaj przewodnik wprowadzający engineeringterminal.com/computer-science/tutorials/ ...
nalyd88

Odpowiedzi:

188

O ile mi wiadomo, trzy najpopularniejsze sposoby analizowania argumentów wiersza poleceń w C to:

  • Getopt ( #include <unistd.h>z biblioteki POSIX C), który może rozwiązywać proste zadania przetwarzania argumentów . Jeśli jesteś trochę zaznajomiony z bash, to wbudowane getopt w bash jest oparte na Getopt z GNU libc.
  • Argp ( #include <argp.h>z biblioteki GNU C), który może rozwiązywać bardziej złożone zadania i zajmuje się takimi rzeczami, jak na przykład:
    • -?, --helpaby uzyskać pomoc , w tym adres e-mail
    • -V, --versionaby uzyskać informacje o wersji
    • --usagedo wiadomości użytkowania
  • Zrób to sam , czego nie polecam w przypadku programów, które byłyby przekazywane komuś innemu, ponieważ jest zbyt wiele rzeczy, które mogą pójść źle lub obniżyć jakość. Popularny błąd polegający na zapominaniu o „-” w celu zatrzymania analizowania opcji to tylko jeden z przykładów.

Dokumentacja biblioteki GNU C zawiera kilka fajnych przykładów dla Getopt i Argp.

Przykład użycia Getopt

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

int main(int argc, char *argv[])
{
    bool isCaseInsensitive = false;
    int opt;
    enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode = CHARACTER_MODE;

    while ((opt = getopt(argc, argv, "ilw")) != -1) {
        switch (opt) {
        case 'i': isCaseInsensitive = true; break;
        case 'l': mode = LINE_MODE; break;
        case 'w': mode = WORD_MODE; break;
        default:
            fprintf(stderr, "Usage: %s [-ilw] [file...]\n", argv[0]);
            exit(EXIT_FAILURE);
        }
    }

    // Now optind (declared extern int by <unistd.h>) is the index of the first non-option argument.
    // If it is >= argc, there were no non-option arguments.

    // ...
}

Przykład użycia Argp

#include <argp.h>
#include <stdbool.h>

const char *argp_program_version = "programname programversion";
const char *argp_program_bug_address = "<[email protected]>";
static char doc[] = "Your program description.";
static char args_doc[] = "[FILENAME]...";
static struct argp_option options[] = { 
    { "line", 'l', 0, 0, "Compare lines instead of characters."},
    { "word", 'w', 0, 0, "Compare words instead of characters."},
    { "nocase", 'i', 0, 0, "Compare case insensitive instead of case sensitive."},
    { 0 } 
};

struct arguments {
    enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode;
    bool isCaseInsensitive;
};

static error_t parse_opt(int key, char *arg, struct argp_state *state) {
    struct arguments *arguments = state->input;
    switch (key) {
    case 'l': arguments->mode = LINE_MODE; break;
    case 'w': arguments->mode = WORD_MODE; break;
    case 'i': arguments->isCaseInsensitive = true; break;
    case ARGP_KEY_ARG: return 0;
    default: return ARGP_ERR_UNKNOWN;
    }   
    return 0;
}

static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 };

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

    arguments.mode = CHARACTER_MODE;
    arguments.isCaseInsensitive = false;

    argp_parse(&argp, argc, argv, 0, 0, &arguments);

    // ...
}

Przykład robienia tego samemu

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

int main(int argc, char *argv[])
{   
    bool isCaseInsensitive = false;
    enum { CHARACTER_MODE, WORD_MODE, LINE_MODE } mode = CHARACTER_MODE;
    size_t optind;
    for (optind = 1; optind < argc && argv[optind][0] == '-'; optind++) {
        switch (argv[optind][1]) {
        case 'i': isCaseInsensitive = true; break;
        case 'l': mode = LINE_MODE; break;
        case 'w': mode = WORD_MODE; break;
        default:
            fprintf(stderr, "Usage: %s [-ilw] [file...]\n", argv[0]);
            exit(EXIT_FAILURE);
        }   
    }   

    // *argv points to the remaining non-option arguments.
    // If *argv is NULL, there were no non-option arguments.

    // ...
}   

Zastrzeżenie: Jestem nowy w firmie Argp, przykład może zawierać błędy.

Christian Hujer
źródło
9
Naprawdę dokładna odpowiedź, dziękuję Christian (zaopiniowany). Jednak użytkownicy komputerów Mac powinni być świadomi, że podejście argp nie jest zgodne z wieloma platformami. Jak znalazłem tutaj , Argp jest niestandardowym rozszerzeniem API glibc. Jest dostępny w gnulib, więc można go jawnie dodać do projektu. Jednak prawdopodobnie programiści działający tylko na komputery Mac lub międzyplatformowi mogą korzystać z podejścia getopt.
thclark
1
W wersji zrób to sam nie podoba mi się, że opcje pozwalają później na dodatkowy tekst, jak -wzzz analizuje to samo co -w, a także, że opcje muszą znajdować się przed argumentami pliku.
Jake
1
@Jake, masz rację. Szacunek dla dostrzeżenia tego. Nie pamiętam, czy zauważyłem to, kiedy to pisałem. To znowu doskonały przykład, że majsterkowanie jest tak łatwe do popełnienia błędu i dlatego nie powinno się go robić. Dzięki za informację, mogę naprawić przykład.
Christian Hujer
18

Użyj getopt(), a może getopt_long().

int iflag = 0;
enum { WORD_MODE, LINE_MODE } op_mode = WORD_MODE;  // Default set
int opt;

while ((opt = getopt(argc, argv, "ilw") != -1)
{
    switch (opt)
    {
    case 'i':
        iflag = 1;
        break;
    case 'l':
        op_mode = LINE_MODE;
        break;
    case 'w':
        op_mode = WORD_MODE;
        break;
    default:
        fprintf(stderr, "Usage: %s [-ilw] [file ...]\n", argv[0]);
        exit(EXIT_FAILURE);
    }
}

/* Process file names or stdin */
if (optind >= argc)
    process(stdin, "(standard input)", op_mode);
else
{
    int i;
    for (i = optind; i < argc; i++)
    {
        FILE *fp = fopen(argv[i], "r");
        if (fp == 0)
            fprintf(stderr, "%s: failed to open %s (%d %s)\n",
                    argv[0], argv[i], errno, strerror(errno));
        else
        {
            process(fp, argv[i], op_mode);
            fclose(fp);
        }
    }
 }

Zwróć uwagę, że musisz określić, które nagłówki mają być uwzględnione (robię to 4, które są wymagane), a sposób, w jaki napisałem op_modetyp, oznacza, że ​​masz problem z funkcją process()- nie możesz uzyskać dostępu do wyliczenia tam na dole. Najlepiej jest przenieść wyliczenie poza funkcję; możesz nawet utworzyć op_modezmienną o zasięgu plikowym bez powiązania zewnętrznego (fantazyjny sposób powiedzenia static), aby uniknąć przekazywania jej do funkcji. Ten kod nie -jest synonimem standardowego wejścia, innym ćwiczeniem dla czytelnika. Zauważ, że getopt()automatycznie dba o --zaznaczenie końca opcji za Ciebie.

Nie uruchomiłem żadnej wersji powyższego wpisywania poza kompilatorem; mogą być w nim błędy.


Aby uzyskać dodatkowe punkty, napisz funkcję (biblioteczną):

int filter(int argc, char **argv, int idx, int (*function)(FILE *fp, const char *fn));

który hermetyzuje logikę przetwarzania opcji nazw plików po getopt()pętli. Powinien obsługiwać -jako standardowe wejście. Zauważ, że użycie tego wskazywałoby, że op_modepowinna to być zmienna o zasięgu pliku statycznego. filter()Funkcja przyjmuje argc, argv, optinda wskaźnik do funkcji przetwarzania. Powinien zwrócić 0 (EXIT_SUCCESS), jeśli był w stanie otworzyć wszystkie pliki i wszystkie wywołania funkcji zgłoszone 0, w przeciwnym razie 1 (lub EXIT_FAILURE). Posiadanie takiej funkcji upraszcza pisanie programów filtrujących w stylu uniksowym, które czytają pliki określone w linii poleceń lub na standardowym wejściu.

Jonathan Leffler
źródło
Nie podoba mi się to, że getopt () nie zezwala na opcje po pierwszym pliku.
Jake
POSIX getopt()nie; GNU getopt()robi to domyślnie. Wybieraj. Nie interesują mnie opcje po zachowaniu nazw plików, głównie dlatego, że nie są one niezawodne na różnych platformach.
Jonathan Leffler
14

Zauważyłem, że Gengetopt jest całkiem przydatny - określasz żądane opcje w prostym pliku konfiguracyjnym, a on generuje parę .c / .h, którą po prostu dołączasz i łączysz z aplikacją. Wygenerowany kod korzysta z getopt_long, wydaje się obsługiwać większość typowych parametrów wiersza poleceń i może zaoszczędzić dużo czasu.

Plik wejściowy gengetopt może wyglądać mniej więcej tak:

version "0.1"
package "myApp"
purpose "Does something useful."

# Options
option "filename" f "Input filename" string required
option "verbose" v "Increase program verbosity" flag off
option "id" i "Data ID" int required
option "value" r "Data value" multiple(1-) int optional 

Generowanie kodu jest łatwe i wypluwa cmdline.hi cmdline.c:

$ gengetopt --input=myApp.cmdline --include-getopt

Wygenerowany kod można łatwo zintegrować:

#include <stdio.h>
#include "cmdline.h"

int main(int argc, char ** argv) {
  struct gengetopt_args_info ai;
  if (cmdline_parser(argc, argv, &ai) != 0) {
    exit(1);
  }
  printf("ai.filename_arg: %s\n", ai.filename_arg);
  printf("ai.verbose_flag: %d\n", ai.verbose_flag);
  printf("ai.id_arg: %d\n", ai.id_arg);
  int i;
  for (i = 0; i < ai.value_given; ++i) {
    printf("ai.value_arg[%d]: %d\n", i, ai.value_arg[i]);
  }
}

Jeśli potrzebujesz wykonać dodatkowe sprawdzenie (na przykład upewnienie się, że flagi wykluczają się wzajemnie), możesz to dość łatwo zrobić z danymi przechowywanymi w gengetopt_args_infostrukturze.

davidA
źródło
1 ++ poza tym, że generuje kod, który generuje ostrzeżenia :(
cat
Niestety tak. Dodałem wyjątki do mojego pliku cmake.
davidA
Prawdopodobnie użyję tylko pragm GCC, aby zignorować ostrzeżenia dla tego pliku (okropne, wiem)
cat
Zauważ, że oczywiście stracisz je, jeśli zregenerujesz źródło, więc możesz chcieć zastosować je jako łatkę w procesie kompilacji. Szczerze mówiąc, łatwiej mi było po prostu wyłączyć ostrzeżenia w tych konkretnych plikach.
davidA
cóż, nie, mam na myśli umieszczanie pragm wokół #include, a nie w samym wygenerowanym pliku. dla mnie wyłączenie ostrzeżeń jest verboten :-)
kot
6

Jestem bardzo zaskoczony, że nikt nie wspomniał o pakiecie „opt” Jamesa Theilera.

Możesz znaleźć opt na http://public.lanl.gov/jt/Software/

i pochlebny post z kilkoma przykładami tego, jak to jest o wiele prostsze niż inne podejścia:

http://www.decompile.com/not_invented_here/opt/

markgalassi
źródło
2
@cat Dlaczego uważasz, że od tamtej pory potrzebna jest aktualizacja? To po prostu niewłaściwe podejście do oprogramowania.
Joshua Hedges
@JoshuaHedges Jeśli nie chcę samodzielnie utrzymywać projektu, chcę używać aktywnie zarządzanego kodu w moim aktywnie zarządzanym kodzie. Jest wiele projektów z 2006 roku, które są aktywnie utrzymywane, ale ten zdechł i prawdopodobnie z błędami. Poza tym 2 lata temu (prawie dokładnie!) Dawno temu napisałem, że: P
cat
1
opt nie jest aktywnie utrzymywany, ponieważ jest kompletny i zwarty. Po prostu pobrałem go i próbowałem go zbudować (gcc-7.3) i stwierdziłem, że biblioteka buduje się i działa, ale test C ++ przydałby się niewielką pracą. iostream.h powinien stać się iostream i używać namespace std; należy dodać. Wspomnę o tym Jamesowi. Dotyczy to tylko testu interfejsu API C ++, a nie samego kodu.
markgalassi
4

Docopt ma implementację C, która moim zdaniem była całkiem niezła: https://github.com/docopt/docopt.c

Ze standardowego formatu strony podręcznika opisującego opcje wiersza poleceń docopt wnioskuje i tworzy parser argumentów. Zaczęło się to w Pythonie; wersja dla Pythona dosłownie po prostu analizuje docstring i zwraca dict. Aby to zrobić w C, wymaga trochę więcej pracy, ale jest czysty w użyciu i nie ma żadnych zewnętrznych zależności.

gvoysey
źródło
3

Istnieje wielka biblioteka libUCW ogólnego przeznaczenia, która zawiera zgrabną analizę opcji wiersza poleceń i ładowanie pliku konfiguracyjnego .

Biblioteka jest również dostarczana z dobrą dokumentacją i zawiera kilka innych przydatnych rzeczy (szybkie IO, struktury danych, alokatory, ...), ale można ich używać osobno.

Przykładowy parser opcji libUCW (z dokumentacji biblioteki)

#include <ucw/lib.h>
#include <ucw/opt.h>

int english;
int sugar;
int verbose;
char *tea_name;

static struct opt_section options = {
  OPT_ITEMS {
    OPT_HELP("A simple tea boiling console."),
    OPT_HELP("Usage: teapot [options] name-of-the-tea"),
    OPT_HELP(""),
    OPT_HELP("Options:"),
    OPT_HELP_OPTION,
    OPT_BOOL('e', "english-style", english, 0, "\tEnglish style (with milk)"),
    OPT_INT('s', "sugar", sugar, OPT_REQUIRED_VALUE, "<spoons>\tAmount of sugar (in teaspoons)"),
    OPT_INC('v', "verbose", verbose, 0, "\tVerbose (the more -v, the more verbose)"),
    OPT_STRING(OPT_POSITIONAL(1), NULL, tea_name, OPT_REQUIRED, ""),
    OPT_END
  }
};

int main(int argc, char **argv)
{
  opt_parse(&options, argv+1);
  return 0;
}
Tomáš Gavenčiak
źródło
opcja pozycyjna ma błąd. jeśli istnieją dwa OPT_STRING, a jeden jest pozycyjny, drugi nie, nie może przeanalizować.
NewBee
2

Napisałem niewielką bibliotekę, która analizuje argumenty podobne do POpt, z którą miałem kilka problemów, o nazwie XOpt . Używa analizy argumentów w stylu GNU i ma bardzo podobny interfejs do POpt.

Używam go od czasu do czasu z dużym powodzeniem i działa praktycznie wszędzie.

Qix - MONICA ZOSTAŁA POMYŚLNA
źródło
1

Jeśli mogę, tootując swój własny klakson, chciałbym również zasugerować przyjrzenie się bibliotece opcji parsującej, którą napisałem: dropt .

  • Jest to biblioteka C (w razie potrzeby z opakowaniem C ++).
  • Jest lekki.
  • Jest rozszerzalny (niestandardowe typy argumentów można łatwo dodawać i mają równe podstawy z wbudowanymi typami argumentów).
  • Powinien być bardzo przenośny (napisany w standardzie C) bez żadnych zależności (poza biblioteką standardową C).
  • Ma bardzo nieograniczoną licencję (zlib / libpng).

Jedną z funkcji, którą oferuje, a której wielu innych nie ma, jest możliwość zastąpienia wcześniejszych opcji. Na przykład, jeśli masz alias powłoki:

alias bar="foo --flag1 --flag2 --flag3"

i chcesz używać, barale z --flag1wyłączoną funkcją, umożliwia:

bar --flag1=0
jamesdlin
źródło
0
#include <stdio.h>

int main(int argc, char **argv)
{
    size_t i;
    size_t filename_i = -1;

    for (i = 0; i < argc; i++)
    {
        char const *option =  argv[i];
        if (option[0] == '-')
        {
            printf("I am a flagged option");
            switch (option[1])
            {
                case 'a':
                    /*someting*/
                    break;
                case 'b':
                    break;
                case '-':
                    /* "--" -- the next argument will be a file.*/
                    filename_i = i;
                    i = i + 1;
                    break;
                default:
                    printf("flag not recognised %s", option);
                    break;
            }
        }
        else
        {   
            printf("I am a positional argument");
        }

        /* At this point, if -- was specified, then filename_i contains the index
         into argv that contains the filename. If -- was not specified, then filename_i will be -1*/
     }
  return 0;
}
Strąk
źródło
4
Nie; absolutnie nie jest to dobry sposób ... Użyj jednej z funkcji analizujących argumenty - getopt()lub getopt_long().
Jonathan Leffler
5
Brzmi jak oszustwo, biorąc pod uwagę, że jest to ewidentnie pytanie do pracy domowej. Ponadto OP ma trudności ze zrozumieniem koncepcji tego, czym jest ciąg i jak odczytywać jego części. Narzucanie mu getopów jest błędem.
Pod
To jest zadanie domowe. Wiem, co to jest sznurek. Po prostu nie rozumiem, jak rozbijać argumenty wiersza poleceń, ponieważ wydaje mi się to zagmatwane, kiedy możesz wprowadzić opcje dowolną liczbę razy, więc nie możesz naprawdę dowiedzieć się, gdzie są nazwy plików. Może za bardzo się nad tym zastanawiam?
user1251020
0

Szablon instrukcji do analizowania argumentów wiersza poleceń w C.

C:> nazwa programu -w - plikOne.txt plikTwo.txt

BOOL argLine = FALSE;
BOOL argWord = FALSE;
BOOL argChar = FALSE;
char * fileName1 = NULL;
char * fileName2 = NULL;

int main(int argc, char * argv[]) {
    int i;
    printf("Argument count=%d\n",argc);
    for (i = 0; i < argc; i++) {
        printf("Argument %s\n",argv[i]);
        if (strcmp(argv[i],"-l")==0) {
            argLine = TRUE;
            printf("    argLine=TRUE\n");
        }
        else if (strcmp(argv[i],"-w")==0) {
            argWord = TRUE;
            printf("    argWord=TRUE\n");
        }
        else if (strcmp(argv[i],"-c")==0) {
            argChar = TRUE;
            printf("    argChar=TRUE\n");
        }
        else if (strcmp(argv[i],"--")==0) {
            if (i+1 <= argc) {
                fileName1 = argv[++i];
                printf("    fileName1=%s\n",fileName1);
            }
            if (i+1 <= argc) {
                fileName2 = argv[++i];
                printf("    fileName2=%s\n",fileName2);
            }
        }
    }
    return 0;
}
Java42
źródło
1
... Nie sądzę, że istnieje zmienna boolowska w C ...?
user1251020
Moje środowisko eclipse / windows ma typ BOOL. Po prostu zmień go na wpisanie int lub char i odpowiednio dostosuj kod.
Java 42,
1
C99 ma zawsze typ _Booli nagłówek, <stdbool.h>który definiuje booljako _Booli truei falseoraz __bool_true_false_are_definedwszystkie makra (które wyjątkowo mogą być niezdefiniowane i przedefiniowane bez wywoływania niezdefiniowanego zachowania; ta licencja jest jednak oznaczona jako „przestarzała”). Jeśli więc masz kompilator C99, możesz użyć <stdbool.h>i bool. Jeśli nie, albo napiszesz go dla siebie (nie jest to trudne), albo użyjesz natywnego odpowiednika.
Jonathan Leffler
1
@Wolfer Moje środowisko C ma typ BOOL (jak typedef int BOOL) i typ boolean (jako typedef unsigned char boolean) i nie ma definicji typu bool. W tym przykładzie po prostu zmień typ na int lub char i odpowiednio dostosuj kod.
Java42
3
Nie zgadzam się z tym podejściem. Użyj funkcji biblioteki, aby przeanalizować opcje.
Jonathan Leffler,
0
    /*
      Here's a rough one not relying on any libraries.
      Example:
      -wi | -iw //word case insensitive
      -li | -il //line case insensitive
      -- file  //specify the first filename (you could just get the files
      as positional arguments in the else statement instead)
      PS: don't mind the #define's, they're just pasting code :D
    */
    #ifndef OPT_H
    #define OPT_H

    //specify option requires argument
    #define require \
      optarg = opt_pointer + 1; \
      if (*optarg == '\0') \
      { \
        if (++optind == argc) \
          goto opt_err_arg; \
        else \
          optarg = argv[optind]; \
      } \
      opt_pointer = opt_null_terminator;

    //start processing argv
    #define opt \
    int   optind                 = 1; \
    char *opt_pointer            = argv[1]; \
    char *optarg                 = NULL; \
    char  opt_null_terminator[2] = {'\0','\0'}; \
    if (0) \
    { \
      opt_err_arg: \
        fprintf(stderr,"option %c requires argument.\n",*opt_pointer); \
        return 1; \
      opt_err_opt: \
        fprintf(stderr,"option %c is invalid.\n",*opt_pointer); \
        return 1; \
    } \
    for (; optind < argc; opt_pointer = argv[++optind]) \
      if (*opt_pointer++ == '-') \
      { \
        for (;;++opt_pointer) \
          switch (*opt_pointer) \
          {

    //stop processing argv
    #define done \
          default: \
            if (*opt_pointer != '\0') \
              goto opt_err_opt; \
            else \
              goto opt_next; \
            break; \
          } \
        opt_next:; \
      }
    #endif //opt.h

    #include <stdio.h>
    #include "opt.h"
    int
    main (int argc, char **argv)
    {
      #define by_character 0
      #define by_word      1
      #define by_line      2
      int cmp = by_character;
      int case_insensitive = 0;
      opt
      case 'h':
        puts ("HELP!");
        break;
      case 'v':
        puts ("fileCMP Version 1.0");
        break;
      case 'i':
        case_insensitive = 1;
        break;
      case 'w':
        cmp = by_word;
        break;
      case 'l':
        cmp = by_line;
        break;
      case '-':required
        printf("first filename: %s\n", optarg);
        break;
      done
      else printf ("Positional Argument %s\n", argv[optind]);
      return 0;
    }
Anonimowy
źródło
2
Musisz wyjaśnić swój kod, a nie tylko rzucać nim w górę i oczekiwać, że wszyscy go zrozumieją. To jest strona do nauki nie tylko kopiowania i wklejania.
Yokai
0

Okay to początek długiej historii - zrobiłem krótko 'bort parsowanie wiersza poleceń w C ...

/**
* Helper function to parse the command line
* @param argc Argument Counter
* @param argv Argument Vector
* @param prog Program Instance Reference to fill with options
*/
bool parseCommandLine(int argc, char* argv[], DuplicateFileHardLinker* prog) {
  bool pathAdded = false;

  // iterate over all arguments...
  for ( int i = 1; i<argc; i++ ) {

    // is argv a command line option ?
    if ( argv[i][0] == '-' || argv[i][0] == '/' ) {

// ~~~~~~ Optionally Cut that part vvvvvvvvvvvvv for sake of simplicity ~~~~~~~
      // check for longer options
            if ( stricmp( &argv[i][1], "NoFileName"  ) == 0
              ||  strcmp( &argv[i][1], "q1"          ) == 0 ) {

        boNoFileNameLog = true;
      } else if ( strcmp( &argv[i][1], "HowAreYou?"    ) == 0 ) {
          logInfo( "SECRET FOUND: Well - wow I'm glad ya ask me.");
      } else {

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Now here comes the main thing:
//
        // check for one char options
        while ( char option = *++argv[i] ) {

          switch ( option ) {
          case '?':
            // Show program usage

            logInfo(L"Options:");
            logInfo(L"  /q\t>Quite mode");
            logInfo(L"  /v\t>Verbose mode");
            logInfo(L"  /d\t>Debug mode");
            return false;

            // Log options
          case 'q':
            setLogLevel(LOG_ERROR);
            break;

          case 'v':
            setLogLevel(LOG_VERBOSE);
            break;

          case 'd':
            setLogLevel(LOG_DEBUG);
            break;

          default:
            logError(L"'%s' is an illegal command line option!"
                      "  Use /? to see valid options!", option);
            return false;
          } // switch one-char-option
        } //while one-char-options
      }  //else one vs longer options
    } // if isArgAnOption

// 
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^  So that's it! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// What follows now is are some usefull extras...
//
    else {


      // the command line options seems to be a path...
      WCHAR tmpPath[MAX_PATH_LENGTH];
      mbstowcs(tmpPath, argv[i], sizeof(tmpPath));

      // check if the path is existing!
      //...

      prog->addPath(tmpPath); //Comment or remove to get a working example
      pathAdded = true;
    }
  }

  // check for parameters
  if ( !pathAdded ) {
    logError("You need to specify at least one folder to process!\n"
             "Use /? to see valid options!");
    return false;
  }

  return true;
}



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

  try {
    // parse the command line
    if ( !parseCommandLine(argc, argv, prog) ) {
      return 1; 
    }

// I know that sample is just to show how the nicely parse commandline Arguments
// So Please excuse more nice useful C-glatter that follows now...
  }
  catch ( LPCWSTR err ) {
    DWORD dwError = GetLastError();
    if ( wcslen(err) > 0 ) {
      if ( dwError != 0 ) {
        logError(dwError, err);
      }
      else {
        logError(err);
      }
    }
    return 2;
  }
}

#define LOG_ERROR               1
#define LOG_INFO                0
#define LOG_VERBOSE             -1
#define LOG_DEBUG               -2

/** Logging Level for the console output */
int logLevel = LOG_INFO;

void logError(LPCWSTR message, ...) {
  va_list argp;
  fwprintf(stderr, L"ERROR: ");
  va_start(argp, message);
  vfwprintf(stderr, message, argp);
  va_end(argp);
  fwprintf(stderr, L"\n");
}


void logInfo(LPCWSTR message, ...) {
  if ( logLevel <= LOG_INFO ) {
    va_list argp;
    va_start(argp, message);
    vwprintf(message, argp);
    va_end(argp);
    wprintf(L"\n");
  }
}

Zauważ, że ta wersja obsługuje również łączenie argumentów: Więc zamiast pisać / h / s -> / hs również będzie działać.

Przepraszam, że jestem n-tą osobą, która publikuje tutaj - jednak nie byłem zadowolony ze wszystkich wersji samodzielnych, które tu widziałem. Cóż, te lib są całkiem fajne. Więc wolałbym parser opcji libUCW , Arg lub Getopt niż te wykonane w domu.

Uwaga, możesz zmienić:

*++argv[i]-> (++argv*)[0] już mniej tajemniczy, ale wciąż tajemniczy.

Dobra, podzielmy to: 1. argv [i] -> uzyskaj dostęp do i-tego elementu w polu wskaźnika argv-char

  1. ++ * ... -> przekaże wskaźnik argv o jeden znak

  2. ... [0] -> będzie podążać za wskaźnikiem, odczytując znak

  3. ++ (...) -> nawias jest dostępny, więc zwiększymy wskaźnik, a nie samą wartość znaku.

Tak fajnie, że w C ## wskaźniki „umarły” - niech żyją wskaźniki !!!

Nadu
źródło