Analysieren von Befehlszeilenargumenten in C

Lesezeit: 15 Minuten

Benutzeravatar von user1251020
Benutzer1251020

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


Benutzeravatar von Christian Hujer
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.

Beispiel für die Verwendung 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.

    // ...
}

Beispiel für die Verwendung 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);

    // ...
}

Beispiel für Selber machen

#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.

    – Jay Lee

    21. April 2021 um 14:22 Uhr


Benutzeravatar von Jonathan Leffler
Jonathan Leffler

Verwenden getopt()oder vielleicht 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);
        }
    }
 }

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:

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

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 🙂

    – Katze

    3. Oktober 2016 um 20:28 Uhr

Benutzeravatar von markgalassi
markgalassi

Sie können James Theilers verwenden “opt”-Paket.

Und ein schmeichelhafter Beitrag mit einigen Beispielen dafür, wie es so viel einfacher ist als andere Ansätze, ist hier:

Überprüfung und Upgrades von Opt 3.19

Benutzeravatar von gvoysey
gvoysey

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.

Benutzeravatar von Peter Mortensen
Peter Mortensen

Es gibt eine großartige Allzweck-C-Bibliothek, libUCWwas ordentlich beinhaltet Parsing von Befehlszeilenoptionen und Konfigurationsdatei laden.

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;
}

Benutzeravatar von jamesdlin
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

1423450cookie-checkAnalysieren von Befehlszeilenargumenten in C

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy