40 Stimmen

String in argv/argc parsen

Gibt es eine Möglichkeit in C, einen Text zu analysieren und Werte für argv und argc zu erhalten, als ob der Text an eine Anwendung über die Befehlszeile übergeben worden wäre?

Dies muss nicht auf Windows funktionieren, nur auf Linux - mir ist auch die Anführung von Argumenten egal.

0 Stimmen

Für welche Plattform? Wie Befehlszeilen in argc/argv umgewandelt werden, unterscheidet sich erheblich zwischen Windows und UNIX-basierten Systemen, zum Beispiel. Auf UNIX transformiert die Shell in der Regel die Befehlszeile erheblich, einschließlich des Durchlaufens (Dateimustererweiterung) und der Variablensubstitution. Auf Windows wird die Dateimustererweiterung nicht von der Shell durchgeführt (es sei denn, Sie verwenden etwas wie Cygwin oder das MKS Toolkit).

0 Stimmen

Wenn Sie nicht einmal mit zitierten Argumenten umgehen müssen, würde ich wirklich vorschlagen, Ihre eigene Funktion zu codieren, anstatt eine Bibliothek von Drittanbietern nur für diese Aufgabe einzuführen.

2 Stimmen

Hast du getopt() ausprobiert? (man 3 getopt). Du kannst die Quellen für die meisten Standard-UNIX/Linux-Tools für Beispiele einsehen, eine RIESIGE Anzahl davon. Selbst die Man-Page (zumindest die Linux-Version) enthält ein ordentliches Beispiel. Es gibt auch eine Reihe von Wrapper-Funktionen (du siehst hier Empfehlungen), aber getopt() scheint die einzige verfügbare für JEDE UNIX-Plattform zu sein (tatsächlich scheint sie Teil des POSIX-Standards zu sein).

1voto

Lê Quang Duy Punkte 757

Mein Projekt erfordert das Aufteilen eines Strings in argc und argv.

Habe einen ziemlich ausgezeichneten Code von Torek gefunden. Aber er verändert den Eingabepuffer, also habe ich einige Anpassungen vorgenommen, um meinen Bedürfnissen gerecht zu werden.

Ich habe einfach ein wenig mehr hinzugefügt, um das Mischen von Anführungszeichen bei der Eingabe in der Befehlszeile zu handhaben, damit sich das Verhalten mehr (aber nicht komplett) wie die Linux-Shell verhält.

Hinweis: Diese Funktion bearbeitet den Original-String nicht, sodass Sie den Eingabepuffer wiederverwenden können (Fehlerbericht, usw.).

void remove_quote(char* input){
   //Implementieren Sie selbst das Entfernen von Anführungszeichen, damit es komplett wie die Linux-Shell ist
}
size_t cmd_param_split(char *buffer, char *argv[], size_t argv_max_size)
{
    char *p, *start_of_word;
    int c, i;
    enum states { DULL=0, IN_WORD, IN_STRING, QUOTE_DOUBLE,QUOTE_SINGLE } state = DULL;
    size_t argc = 0;
    int quote = 0;
    for (p = buffer; argc < argv_max_size && *p != '\0'; p++) {
        c = (unsigned char) *p;
        printf("Verarbeite %c, Zustand = %d\n", c,state);
        switch (state) {
        case DULL:
            if (isspace(c)) {
                continue;
            }

            if (c == '"' ||c == '\'') {
                quote = c;
                state = IN_STRING;
                start_of_word = p + 1;
                continue;
            }
            state = IN_WORD;
            start_of_word = p;
            continue;

        case IN_STRING:
            if (c == '"' || c == '\'') {
                if (c!=quote)
                    continue;
                else
                    quote = 0;
                strncpy(argv[argc],start_of_word, p - start_of_word);
                remove_quote(argv[argc]);
                argc++;
                state = DULL;
            }
            continue;

        case IN_WORD:
            if(quote==0 && (c == '\"' ||c == '\''))
                quote = c;
            else if (quote == c)
                    quote = 0;

            if (isspace(c) && quote==0) {
                strncpy(argv[argc],start_of_word, p - start_of_word);
                remove_quote(argv[argc]);
                argc++;
                state = DULL;
            }
            continue;
        }
    }

    if (state != DULL && argc < argv_max_size){
        strncpy(argv[argc],start_of_word, p - start_of_word);
        remove_quote(argv[argc]);
        argc++;
    }

    if (quote){
        printf("WARNUNG: Anführungszeichen sind nicht ausgeglichen. Das könnte zu unerwünschtem Verhalten führen\n");
        for(i = 0;i

`

Getestet mit den folgenden Zeichenfolgen

 1. "1 2 3 \'3 4\"567\' \"bol\'obala\" 2x2=\"foo\""
   arg 0 = [1]
   arg 1 = [2]
   arg 2 = [3]
   arg 3 = [3 4"567]
   arg 4 = [bol'obala]
   arg 5 = [2x2="foo"]
 2. "./foo bar=\"Hanoi HoChiMinh\" exp='foo123 \"boo111' mixquote \"hanoi \'s\""
   arg 0 = [./foo]
   arg 1 = [bar="Hanoi HoChiMinh"]
   arg 2 = [exp='foo123 "boo111']
   arg 3 = [mixquote]
   arg 4 = [hanoi 's]

Jedoch würde die Linux-Shell Anführungszeichen entfernen, selbst bei gemischtem Fall, wie unten gezeigt, wenn sie vom Befehlszeilenfenster aus ausgeführt wird, getestet auf einem RaspberryPi.

./foo bar="Hanoi HoChiMinh" exp='foo123 "boo111' mixquote "hanoi 's"
   arg 0 = [./foo]
   arg 1 = [bar=Hanoi HoChiMinh]
   arg 2 = [exp=foo123 "boo111]
   arg 3 = [mixquote]
   arg 4 = [hanoi 's]

Also, wenn Sie wirklich das gesamte Verhalten der Linux-Shell nachahmen möchten, geben Sie einfach ein wenig mehr Mühe in die Entfernung der Anführungszeichen in der remove_quote() Funktion, wie ich oben leer gelassen habe.

`

0voto

bua Punkte 4611

Leider C++ aber für andere, die nach dieser Art von Bibliothek suchen, empfehle ich:

ParamContainer - einfach zu verwendender Befehlszeilenparameter-Parser

Wirklich klein und wirklich einfach.

p.addParam("lang-name", 'n', ParamContainer::regular, 
           "Parameterbeschreibung", "Standardwert");  

programmname --lang-name=wert

cout << p["lang-name"];
>> wert

Aus meiner Erfahrung:

  • sehr nützlich und einfach
  • stabil in der Produktion
  • gut getestet (von mir)

0voto

PinkTurtle Punkte 6830

Ich musste das in C++ tun, ich habe schließlich auch eine C-Version gemacht.

Diese Version führt keine dynamische Speicherzuweisung durch, sie erkennt doppelte Anführungszeichen, sie erkennt maskierte doppelte Anführungszeichen, sie erkennt einfache Anführungszeichen (einfache Anführungszeichen führen keine Maskierung durch).

Es ist die Verantwortung des Aufrufers, einen mit null initialisierten temporären Puffer (das buf-Argument) bereitzustellen, der mindestens so groß ist wie der analysierte Argumenten-String (keine Überprüfungen werden auf die Puffergröße im Parser durchgeführt).

Was ich bemerkt habe, als ich mit Befehlszeilenargumenten herumgespielt habe:

  • Anführungszeichen (entweder " oder ') definieren kein neues Argument, nur Leerzeichen tun dies. Zum Beispiel wird a"b c" zu einem einzelnen Argument ab c.

  • Einfache Anführungszeichen führen keine Maskierung auf ihren Inhalt durch. Zum Beispiel wird 'hello\' zum Argument hello\.

Wenn es eine Spezifikation gibt, wie C/C++-Programme ihre Befehlszeilenargumente analysieren, würde ich gerne darüber erfahren.

Live gdb: https://onlinegdb.com/i_6_G-u5E

Implementierung:

#include <string.h>
#include <stdbool.h>

/*
    in args         der zu analysierende String
    in buf          ein null-initialisierter Puffer mit der Größe "strlen(args) + 1" oder größer
    out argc        Anzahl der Argumente
    out argv        ein Char*-Array-Puffer
    argvlen         Größe des argv-Arrays
*/
void str_to_argc_argv(const char* args, char* buf, int* argc, char** argv, int argvlen) {
    enum PARSER_STATE {
        GET_CHAR,
        SEEK_DOUBLE_QUOTE_END,
        SEEK_SINGLE_QUOTE_END,

    } parser_state = GET_CHAR;

    size_t commit = 0;//Anzahl der übertragenen Zeichen (von "args" in "buf")
    for (size_t c = 0; c < strlen(args); c++) {
        switch (parser_state) {
        case GET_CHAR: {
            switch (args[c]) {
            case ' ':
            case '\t': {
                if (commit != 0 && buf[commit - 1] != '\0') {//führende Leerzeichen ignorieren
                    buf[commit++] = '\0';
                }
                break;
            }
            case '"': {
                parser_state = SEEK_DOUBLE_QUOTE_END;
                break;
            }
            case '\'': {
                parser_state = SEEK_SINGLE_QUOTE_END;
                break;
            }
            default: {
                buf[commit++] = args[c];
                break;
            }
            }
            break;
        }
        case SEEK_DOUBLE_QUOTE_END: {
            switch (args[c]) {
            case '"': {
                bool escaped = args[c - 1] == '\\' && args[c - 2] != '\\';//sicher (c kann hier nicht kleiner als 2 sein)
                if (!escaped) {
                    parser_state = GET_CHAR;
                }
                else {
                    buf[commit++] = '"';
                }
                break;
            }
            default: {
                buf[commit++] = args[c];
                break;
            }
            }
            break;
        }
        case SEEK_SINGLE_QUOTE_END: {
            switch (args[c]) {
            case '\'': {
                parser_state = GET_CHAR;
                break;
            }
            default: {
                buf[commit++] = args[c];
                break;
            }
            }
            break;
        }
        }
    }

    int argc_ = 0;
    while (*buf != '\0' && argc_ < argvlen) {
        *(argv + argc_++) = buf;
        buf += strlen(buf) + 1;
    }
    *argc = argc_;
}

Test:

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

#define MAX_ARGS 128
#define BUFFER_LENGTH 128

int main(int argc, char** argv)//Befehlszeilenargument: hello "world" -opt "an op\"tion'" 'bla \' bla' "blalba"'
{
    for (size_t a = 0; a < argc; a++) {
        puts(argv[a]);
    }
    puts("\n");

    const char* args = "   hello \"world\" -opt \"an op\\\"tion'\" 'bla \\' bla' \"blalba\"'";
    char buf[BUFFER_LENGTH] = { 0 };//muss mindestens die Größe von ^ haben und mit 0 initialisiert sein.

    int argc_;              //Rückgabewert
    char* argv_[MAX_ARGS];  //Rückgabewert

    str_to_argc_argv(args, buf, &argc_, argv_, MAX_ARGS);

    for (size_t a = 0; a < argc_; a++) {
        puts(argv_[a]);
    }

    return 0;
}

Bearbeiten:

Der escaped-Test sollte stattdessen diese Funktion verwenden:

bool escaped(const char* args, size_t pos) {
    bool escaped_ = false;
    for (size_t c = pos - 1; c != 0; c--) {
        if (args[c] == '\\') {
            escaped_ = !escaped_;
        }
        else {
            return escaped_;
        }
    }
    return escaped_;
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X