Ich versuche, ein Programm zu schreiben, das zwei Dateien Zeile für Zeile, Wort für Wort oder Zeichen für Zeichen in C vergleichen kann. Es muss in der Lage sein, Befehlszeilenoptionen einzulesen -l, -w, -i oder --…
wenn die Option ist -lvergleicht es die Dateien Zeile für Zeile.
wenn die Option ist -wvergleicht es die Dateien Wort für Wort.
wenn die Option ist --wird automatisch davon ausgegangen, dass das nächste Argument der erste Dateiname ist.
wenn die Option ist -ivergleicht es sie ohne Berücksichtigung der Groß-/Kleinschreibung.
vergleicht die Dateien standardmäßig Zeichen für Zeichen.
Es soll keine Rolle spielen, wie oft die Optionen eingegeben werden, solange -w und -l nicht gleichzeitig eingegeben werden und es gibt nicht mehr oder weniger als zwei Dateien.
Ich weiß nicht einmal, wo ich mit dem Parsen der Befehlszeilenargumente anfangen soll.
Das ist also der Code, den ich mir für alles ausgedacht habe. Ich habe es noch nicht ganz auf Fehler überprüft, aber schreibe ich die Dinge zu kompliziert?
/*
* 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;
}
Also, gehen Sie und lesen Sie die Handbuchseite dafür; es ist nicht sehr komplex, und die Handbuchseite enthält wahrscheinlich ein Beispiel, mit dem Sie experimentieren können (und wenn Ihre lokale Handbuchseite dies nicht tut, finden Sie sicherlich Beispiele im Internet).
– Jonathan Leffler
10. März 2012 um 0:54 Uhr
Dies ist eine High-Level-Bibliothek: argparse in c, sehr einfach zu bedienen.
– Cofiz
15. Januar 2015 um 2:49 Uhr
stackoverflow.com/questions/189972/…
– jamesdlin
4. Oktober 2016 um 3:43 Uhr
Whoa, das sind eine Menge strcmps :q
– BarbaraKwarc
4. Juli 2018 um 19:27 Uhr
Beachten Sie, dass -- hat eine standardisierte Bedeutung (POSIX schreibt dies vor!), die nicht vorweggenommen werden sollte. Es bedeutet „Ende der Optionsargumente“. Dies, wenn Sie schreiben grep -e -elephant -- -* das -elephant Option ist die Regex, nach der gesucht werden soll (obwohl beginnend mit -), aber die -- markiert das Ende der Optionen, also alle Dateinamen, die übereinstimmen -* (oder -* wenn keine übereinstimmen und Sie Bash nicht mit verwenden shopt -s nullglob set) werden als Dateinamen behandelt, nicht als Optionen zu grep. Wenn Sie gerne gefährlich leben, können Sie einen Dateinamen entfernen -fr verwenden rm -- -fr (Aber es ist besser zu verwenden rm -- ./-fr).
– Jonathan Leffler
25. Oktober 2018 um 0:33 Uhr
Christian Hüjer
Meines Wissens nach sind die drei beliebtesten Methoden zum Analysieren von Befehlszeilenargumenten in C:
Getopt (#include <unistd.h> aus der POSIX C-Bibliothek), die gelöst werden können einfache Argumentanalyse Aufgaben. Wenn Sie mit Bash ein wenig vertraut sind, basiert das in Bash integrierte Getopt auf Getopt aus der GNU-Libc.
Argp (#include <argp.h> aus der GNU C Library), die mehr lösen kann komplexe Aufgaben und kümmert sich um Dinge wie zum Beispiel:
-?, --help zum Hilfenachrichteinschließlich E-Mail-Addresse
-V, --version zum Versionsinformation
--usage zum Nutzungsmeldung
Selber machen, was ich nicht für Programme empfehle, die an jemand anderen weitergegeben würden, da zu viel schief gehen oder die Qualität beeinträchtigen könnte. Der beliebte Fehler, ‘–‘ zu vergessen, um das Analysieren von Optionen zu stoppen, ist nur ein Beispiel.
Die Dokumentation der GNU C Library enthält einige nette Beispiele für Getopt und Argp.
#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.
// ...
}
#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 += optind;
// *argv points to the remaining non-option arguments.
// If *argv is NULL, there were no non-option arguments.
// ...
}
Haftungsausschluss: Ich bin neu bei Argp, das Beispiel kann Fehler enthalten.
Wirklich gründliche Antwort, danke Christian (aufgewertet). Mac-Benutzer sollten sich jedoch darüber im Klaren sein, dass der argp-Ansatz nicht plattformübergreifend kompatibel ist. Wie ich fand hierArgp ist eine nicht standardisierte Glibc-API-Erweiterung. Es ist in gnulib verfügbar kann also explizit zu einem Projekt hinzugefügt werden. Für reine Mac- oder plattformübergreifende Entwickler ist es jedoch wahrscheinlich einfacher, den getopt-Ansatz zu verwenden.
– thclark
13. Dezember 2016 um 11:06 Uhr
Bei der Do-it-yourself-Version gefällt es mir nicht, dass die Optionen nachträglich zusätzlichen Text zulassen, wie -wzzz dasselbe wie -w parst, und auch, dass die Optionen vor den Dateiargumenten stehen müssen.
– Jake
13. Oktober 2019 um 2:56 Uhr
@Jake du hast recht. Respekt, dass du das bemerkt hast. Ich weiß nicht mehr, ob mir das beim Schreiben aufgefallen ist. Es ist wieder ein perfektes Beispiel dafür, dass DIY so leicht falsch gemacht werden kann und daher nicht gemacht werden sollte. Danke fürs Mitteilen, ich könnte das Beispiel korrigieren.
– Christian Hüjer
2. November 2019 um 6:54 Uhr
Das ist nur eine Spitzfindigkeit –*argv zeigt nicht auf die verbleibenden Nicht-Optionsargumente! argv += optind oder ähnliches hinzugefügt werden. Wie @ChristianHujer jedoch erwähnte, ist dies ein weiteres Beispiel dafür, dass DIY leicht falsch gemacht werden kann.
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);
}
}
}
Beachten Sie, dass Sie bestimmen müssen, welche Header enthalten sein sollen (ich mache daraus 4, die erforderlich sind) und wie ich die geschrieben habe op_mode Typ bedeutet, dass Sie ein Problem in der Funktion haben process() – Sie können nicht auf die Aufzählung dort unten zugreifen. Es ist am besten, die Aufzählung außerhalb der Funktion zu verschieben; könntest du sogar machen op_mode eine Dateibereichsvariable ohne externe Verknüpfung (eine ausgefallene Art zu sagen static), um eine Übergabe an die Funktion zu vermeiden. Dieser Code funktioniert nicht - als Synonym für Standardeingabe, eine weitere Übung für den Leser. Beachten Sie, dass getopt() kümmert sich automatisch -- um das Ende der Optionen für Sie zu markieren.
Ich habe keine Version der obigen Eingabe über einen Compiler hinaus ausgeführt. da könnten fehler drin sein.
Schreiben Sie für zusätzliche Punkte eine (Bibliotheks-)Funktion:
int filter(int argc, char **argv, int idx, int (*function)(FILE *fp, const char *fn));
die die Logik für die Verarbeitung von Dateinamenoptionen nach dem kapselt getopt() Schleife. Es sollte handhaben - als Standardeingabe. Beachten Sie, dass die Verwendung von this dies anzeigen würde op_mode sollte eine statische Dateibereichsvariable sein. Das filter() Funktion übernimmt argc, argv, optind und einen Zeiger auf die Verarbeitungsfunktion. Es sollte 0 (EXIT_SUCCESS) zurückgeben, wenn es alle Dateien öffnen konnte und alle Aufrufe der Funktion 0 gemeldet haben, andernfalls 1 (oder EXIT_FAILURE). Das Vorhandensein einer solchen Funktion vereinfacht das Schreiben von “Filter”-Programmen im Unix-Stil, die Dateien lesen, die auf der Befehlszeile oder der Standardeingabe angegeben sind.
Ich mag es nicht, dass getopt() keine Optionen nach der ersten Datei zulässt.
– Jake
14. Oktober 2019 um 19:21 Uhr
Posix getopt() nicht; GNU getopt() tut standardmäßig. Treffen Sie Ihre Wahl. Ich bin nicht begeistert von den Optionen nach dem Verhalten von Dateinamen, hauptsächlich weil es nicht plattformübergreifend zuverlässig ist.
– Jonathan Leffler
14. Oktober 2019 um 20:42 Uhr
Ich habe gefunden Gengetopt sehr nützlich sein – Sie geben die gewünschten Optionen mit einer einfachen Konfigurationsdatei an, und es wird ein .c/.h-Paar generiert, das Sie einfach einschließen und mit Ihrer Anwendung verknüpfen. Der generierte Code verwendet getopt_long, scheint die gängigsten Arten von Befehlszeilenparametern zu verarbeiten und kann viel Zeit sparen.
Eine gengetopt-Eingabedatei könnte etwa so aussehen:
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
Das Generieren des Codes ist einfach und spuckt aus cmdline.h und cmdline.c:
Der generierte Code lässt sich einfach integrieren:
#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]);
}
}
Wenn Sie zusätzliche Überprüfungen durchführen müssen (z. B. sicherstellen, dass sich Flags gegenseitig ausschließen), können Sie dies ziemlich einfach mit den in der gespeicherten Daten tun gengetopt_args_info Struktur.
1++, außer dass es Code generiert, der Warnungen generiert 🙁
– Katze
2. Oktober 2016 um 23:06 Uhr
Ja leider. Ich habe Ausnahmen in meine cmake-Datei eingefügt.
– davidA
3. Oktober 2016 um 9:23 Uhr
Ich werde wahrscheinlich nur GCC-Pragmas verwenden, um Warnungen für diese Datei zu ignorieren (schrecklich, ich weiß).
– Katze
3. Oktober 2016 um 13:06 Uhr
Beachten Sie, dass Sie sie natürlich verlieren werden, wenn Sie die Quelle neu generieren, also möchten Sie sie vielleicht als Patch in Ihrem Build-Prozess anwenden. Ehrlich gesagt fand ich es einfacher, Warnungen für diese bestimmten Dateien einfach auszuschalten.
– davidA
3. Oktober 2016 um 20:26 Uhr
Nun, nein, ich meine, die Pragmas um die herum zu legen #include, nicht in der generierten Datei selbst. Für mich ist das Deaktivieren von Warnungen verboten 🙂
Dokopt hat eine C-Implementierung, die ich ganz nett fand:
Aus einem standardisierten Handbuchseitenformat, das Befehlszeilenoptionen beschreibt, leitet docopt ab und erstellt einen Argumentparser. Dies begann in Python; Die Python-Version parst buchstäblich nur den Docstring und gibt ein Diktat zurück. Um dies in C zu tun, ist etwas mehr Arbeit erforderlich, aber es ist sauber zu verwenden und hat keine externen Abhängigkeiten.
Die Bibliothek enthält auch eine gute Dokumentation und enthält einige andere nützliche Dinge (schnelle E/A, Datenstrukturen, Zuweisungen, …), aber diese können separat verwendet werden.
Beispiel für einen libUCW-Optionsparser (aus der Bibliotheksdokumentation)
#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;
}
jamesdlin
Wenn ich darf, tute ich mein eigenes Horn, ich möchte auch vorschlagen, einen Blick auf eine Option-Parsing-Bibliothek zu werfen, die ich geschrieben habe: dropt.
Es ist eine C-Bibliothek (mit einem C++-Wrapper, falls gewünscht).
Es ist leicht.
Es ist erweiterbar (benutzerdefinierte Argumenttypen können einfach hinzugefügt werden und sind mit integrierten Argumenttypen gleichberechtigt).
Es sollte sehr portabel sein (es ist in Standard-C geschrieben) ohne Abhängigkeiten (außer der C-Standardbibliothek).
Es hat eine sehr uneingeschränkte Lizenz (zlib/libpng).
Eine Funktion, die viele andere nicht bieten, ist die Möglichkeit, frühere Optionen zu überschreiben. Wenn Sie beispielsweise einen Shell-Alias haben:
alias bar="foo --flag1 --flag2 --flag3"
und Sie verwenden möchten bar aber mit--flag1 deaktiviert, ermöglicht es Ihnen Folgendes:
bar --flag1=0
14234500cookie-checkAnalysieren von Befehlszeilenargumenten in Cyes
Also, gehen Sie und lesen Sie die Handbuchseite dafür; es ist nicht sehr komplex, und die Handbuchseite enthält wahrscheinlich ein Beispiel, mit dem Sie experimentieren können (und wenn Ihre lokale Handbuchseite dies nicht tut, finden Sie sicherlich Beispiele im Internet).
– Jonathan Leffler
10. März 2012 um 0:54 Uhr
Dies ist eine High-Level-Bibliothek: argparse in c, sehr einfach zu bedienen.
– Cofiz
15. Januar 2015 um 2:49 Uhr
stackoverflow.com/questions/189972/…
– jamesdlin
4. Oktober 2016 um 3:43 Uhr
Whoa, das sind eine Menge strcmps :q
– BarbaraKwarc
4. Juli 2018 um 19:27 Uhr
Beachten Sie, dass
--
hat eine standardisierte Bedeutung (POSIX schreibt dies vor!), die nicht vorweggenommen werden sollte. Es bedeutet „Ende der Optionsargumente“. Dies, wenn Sie schreibengrep -e -elephant -- -*
das-elephant
Option ist die Regex, nach der gesucht werden soll (obwohl beginnend mit-
), aber die--
markiert das Ende der Optionen, also alle Dateinamen, die übereinstimmen-*
(oder-*
wenn keine übereinstimmen und Sie Bash nicht mit verwendenshopt -s nullglob
set) werden als Dateinamen behandelt, nicht als Optionen zugrep
. Wenn Sie gerne gefährlich leben, können Sie einen Dateinamen entfernen-fr
verwendenrm -- -fr
(Aber es ist besser zu verwendenrm -- ./-fr
).– Jonathan Leffler
25. Oktober 2018 um 0:33 Uhr