Kommandozeilenargumente mit '\0'.



  • Hallo,

    folgendes Phänomen:

    #include <stdlib.h>
    
    int main(int argc, char **argv) {
        if (argc<1) return(0);
        printf("argv[1]=\"%s\"",argv[1]);
        return(0);
    }
    

    Unix/Linux:

    $ ./a.out "$(echo -ne "abc\0def\n123\0000456")"
    argv[1]="abcdef
    123456
    

    Meine Vermutung war, dass nur "abc", also alles bis zum '\0', ausgegeben wird.
    Hier zeigt sich jedoch klar, dass '\0' in Parametern...

    1. ...entweder von der Shell entfernt werden...
    2. ...oder C irgendwie alle nul Zeichen aus den Parametern entfernt.

    Da man 1. mit folgendem Befehl ausschliessen kann:

    $ echo -ne "abc\0def" | xargs -0 echo
    abc
    def
    

    ...will ich den Grund für 2. wissen, und wenn möglich, wie man das umgehen kann.

    Wie kann ich '\0' in einem Parameter argv[i] "abfangen".

    Das muss doch irgendwie gehen, oder nicht?

    Danke,
    Gruss.



  • Wenn argc = 1 ist, dann ist argv[0] der gültige Speicherbereich, nicht argv[1].



  • feigling schrieb:

    Wenn argc = 1 ist, dann ist argv[0] der gültige Speicherbereich, nicht argv[1].

    dann eben: if (argc<2) return(0);.

    Die Frage steht immer noch im Raum.

    Irgendwie müssen die ganzen Unixprogramme nul Zeichen in Strings ja trotzdem beherrschen,
    trotz der allgemeinen Stringkonvention in C. Vielleicht wird von der Shell die länge der Args irgendwie mitgeliefert, dann müsste man einfach nur hochzählen, statt auf das erste nul zu warten...



  • ich weiß nicht so genau, was du willst. Welchen Sinn hat es den NUL Zeichen im Parameter zu haben? Ich würde sagen, dass die Shell ein String "abc\0def" als "abcdef" interpretiert:

    $ echo -ne "abc\0def\n123\0000456"
    abcdef
    123456
    

    D.h. mit ./a.out "$(echo -ne "abc\0def\n123\0000456")" wird
    "abcdef
    123456"
    übergeben. Das hat mit C an sich nichts zu tun, echo ist ein builtin command der shell.

    Was C macht ist auch interessant.

    #include <stdio.h>
    #include <string.h>
    
    #include <unistd.h>
    
    #define MAX_CHARS 10
    
    int main(int argc, char **argv)
    {
        int i, len;
    
        if(argc == 1) 
        {  
            pid_t child;
    
            child = fork();
            if(child < 0) 
            {  
                perror("fork");
                return 1; 
            }  
    
            if(child)
            {  
    
                /* parent proc */
                waitpid(child, NULL, 0);
                return 0; 
            }  
    
            /* child proc */
            char str[MAX_CHARS] = { '0', '1', '2', '3', '4', 0, '6', '7', '8', '9'};
            execlp(argv[0], argv[0], str, "ZZZZZZZZZZZZ", NULL);
            return 1;      
        }  
    
        /* child proc after exec */
        printf("argv[1]=\"%s\"\n", argv[1]);
        for(i = 0; i < MAX_CHARS; ++i)
            printf("argv[1][%d] = %c (%d)\n", i, argv[1][i], argv[1][i]);
    
        return 0; 
    }
    

    Ausgabe

    ./a
    argv[1]="01234"
    argv[1][0] = 0 (48)
    argv[1][1] = 1 (49)
    argv[1][2] = 2 (50)
    argv[1][3] = 3 (51)
    argv[1][4] = 4 (52)
    argv[1][5] =  (0)
    argv[1][6] = Z (90)
    argv[1][7] = Z (90)
    argv[1][8] = Z (90)
    argv[1][9] = Z (90)
    

    D.h. Die exec Familie haltet sich an der C-Konvention (was zu erwarten ist, denn wie soll exec herausfinden, wie viele Bytes hinter dem \0 noch stecken?), dass \0 das Ende der Zeichenkette markiert.



  • Ich verstehe nicht, was das für ein unterschied macht, dass echo ein Shell builtin ist, oder nicht.
    Beides ist in C geschrieben, das bash-builtin und /bin/echo.

    Wenn du schreibst:

    $ echo -ne "abc\0def\n123\0000456"
    

    Dann wird im Terminal auf jeden Fall...

    abcdef
    123456
    

    ...angezeigt.

    Der Punkt ist, dass irgendwo und irgendwie im Speicher vom (builtin oder /bin/-) echo unter dem Parameter $2 ("abc\0def\n123\0000456") das nul Zeichen landet, auch wenn es in der normalen Ausgabe nicht dargestellt wird.

    Das kann man zeigen:

    $ echo -ne "abc\0def\n123\0000456" | xargs -0 echo
    abc
    def
    123
    456
    

    Irgendwoher muss xargs ja das '\0' gefunden haben, um die Ausgabe wie oben zu erzeugen. D.h. das nul Zeichen wurde definitif im Parameter des ersten echos übertragen und nicht von der Shell "verschluckt".

    Hier noch ein Beispiel:

    $ echo -en "abc\0def\n123\0000456" | od -a
    0000000   a   b   c nul   d   e   f  nl   1   2   3 nul   4   5   6
    0000017
    

    Das nul Zeichen wird definitiv übergeben. Klar, dass od damit umgehen kann,
    weil od ein File öffnet (/dev/stdin) und das oder stdio ein Stream ist, der alle
    Zeichen, auch nul, enthalten kann, weil in Files der EOF terminiert, nicht nul, wie bei Strings.

    Im echo zuvor wird das nul Zeichen von der Parameterliste eingelesen als String.
    Aber genau das verstehe ich nicht! Wie kann der wissen, dass ein String ein nul Zeichen (/Parameter) enthällt und wie liesst der den Parameter nach dem '\0' weiter/zu Ende???

    Ich hab echt keinen Plan.

    Wenn das '\0' im Parameter als String übergeben wird, wieso hört das C Programm nicht genau an der Stelle auf zu lesen und gibt mehr als nur "abc" aus für den ersten Parameter.

    Ausserdem, nimm das Programm und gib den ersten Parameter nicht als string aus, sondern Zeichen für Zeichen:

    #include <stdlib.h>
    
    int main(int argc, char **argv) {
        if (argc<2) return(0);
        printf("argv[1]=\"%s\"",argv[1]);
        // gilt nur fuer $(echo -ne "abc\0def\n123\0000456")
        int i;
        for (i=0;i<15;i++) {
            printf("argv[1][%d]=\'%c\'\n",i,*argv[1]++);
        }
        return(0);
    }
    

    Nochmal zur Veranschaulichung:

    [/code]$ echo -ne "abc\0def\n123\0000456" | wc -c
    15
    $ echo -en "abc\0def\n123\0000456" | od -a
    0000000 a b c nul d e f nl 1 2 3 nul 4 5 6
    0000017

    Der "abc..." String besteht eindeutig aus 15 Bytes.
    Offensictlich wird das nul Byte "erfasst", nicht "verschluckt".
    ANSI C String Representation hin- oder her.
    
    Dann führe das obige Programm aus mit...
    [code]$ ./test.out "$(echo -ne "abc\0def\n123\0000456")"
    argv[1]="abcdef
    123456"argv[1][0]='a'
    argv[1][1]='b'
    argv[1][2]='c'
    argv[1][3]='d'
    argv[1][4]='e'
    argv[1][5]='f'
    argv[1][6]='
    '
    argv[1][7]='1'
    argv[1][8]='2'
    argv[1][9]='3'
    argv[1][10]='4'
    argv[1][11]='5'
    argv[1][12]='6'
    argv[1][13]=''
    argv[1][14]='2'
    

    Ich habe 15 Bytes in der for Schleife "hardgecodet".

    Was wir hier haben ist eindeutig:
    Das nul Zeichen kommt in meinem C Programm definitiv nicht an.

    argv[1][13]='' <--- das ist das nul Zeichen, welches den String argv[1] (erster Parameter) terminiert.

    argv[1][14]='2' <--- das hier ist schon ein ganz anderer Speicherbereich. Diese "2" hat der irgendwo her, keine Ahnung...

    In meinem C Programm "verschluckt" er die nul Zeichen eindeutig.

    Aber die ganzen Unix Tools können doch offenbar irgendwie mit nul Zeichen in Parametern umgehen, also erklärt mir mal einer wie???

    Ich hoffe mein Problem ist nun glasklar.



  • Der Punkt ist, wenn Du $(echo abc\0def) schreibst, wird das von der Shell expandiert, bevor das nächste Programm in der Kette gestartet wird. Und beim Expandieren fallen die Nullcharacter weg. Beim Pipen natürlich nicht, weil die Shell da nicht mit im Spiel ist.

    Auf der Shell passiert also folgendes:

    Cmd: ./test.out "$(echo -ne "abc\0def\n123\0000456")"
    => Shell: exec("echo", "-ne", "abc\0def\n123\0000456")
    => Ausgabe: "abcdef
    123456"
    Cmd: ./test.out "abcdef
    123456"
    => Shell: exec("./test.out", "abcdef
    123456")
    


  • Dieser Thread wurde von Moderator/in rüdiger aus dem Forum ANSI C in das Forum Linux/Unix verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • ich verstehe immer noch nicht, was ein NUL Zeichen im Parameter zu suchen hat (außer am Ende des Strings natürlich). Ich programmiere seit Jahren unter Unix und bin den Fall nie begegenet, um ehrlich zu sein, du bist der erste, der mich dazu bringt, mich Gedanken darüber zu machen.

    Kannst du erklären, in welchem Fall das vorkommen kann?

    Wie LordJaxom schon sagte: wenn du sowas machst wie "echo ...... | od " dann ist die Ausgabe von echo kein Eingangsparameter sondern wird über eine pipe auf stdin von od geleitet, d.h. dazwischen schaltet sich keiner an. od liest solange die Pipe Daten überträgt und da interessiert es nicht, ob \0 dabei ist oder nicht.

    Wenn ein Programm ein Parameter bekommt, dann muss der aufrufende ein exec ausführen. Die exec Familie haltet sich aber an der Konvention, d.h. nur bis zum ersten \0 Daten übergeben.



  • hmm, ...

    tatsächlich:

    $ echo -ne "$(echo -ne "abc\0def\n123\0000456")" | od -a
    0000000   a   b   c   d   e   f  nl   1   2   3   4   5   6
    0000015
    

    Also angenommen, ich will einen binären Inhalt direkt aus einem Parameter holen.

    $ ./myprog "$(<mybinaryfilewhichcontainsnul)"
    

    Dann habe ich ja so gesehen keine Chance?
    Mein argv[1] ist wegen der C-String Natur niemands 1:1 = mybinaryfilewhichcontainsnul.

    Ja das klärt einiges, ok. Ich habs verstanden.

    PS: Und die Aussage, ein derartiger "Fall" sei einem nie begegnet halte ich übrigends für ein schlechtes Argument, auf dessen Basis man eine (fach-)technische Diskussion führt. Das ist dann so wie wenn der Mann zum Doktor geht ... es tut weh wenn ich so mache... dann machen sie nicht so... naja. egal.



  • Es ist technisch nicht möglich, ein NULL-Byte als Kommandozeileinparameter zu übergeben, da dieses NULL-Byte als Terminator des Parameters genutzt wird. Schon wenn der Prozess mit einem der exec-Systemaufrufe erzeugt wird, werden die Parameter als NULL-Terminierte Strings übergeben.



  • DerGrosseC schrieb:

    Also angenommen, ich will einen binären Inhalt direkt aus einem Parameter holen.

    $ ./myprog "$(<mybinaryfilewhichcontainsnul)"
    

    Dann habe ich ja so gesehen keine Chance?
    Mein argv[1] ist wegen der C-String Natur niemands 1:1 = mybinaryfilewhichcontainsnul.

    Ja das klärt einiges, ok. Ich habs verstanden.

    genau, du hast keine Chance, weil die Parameter C-Strings sind, keine Datenströmme, sonst hieße main so

    int main(size_t max_bytes, char *data);
    

    DerGrosseC schrieb:

    PS: Und die Aussage, ein derartiger "Fall" sei einem nie begegnet halte ich übrigends für ein schlechtes Argument

    In diesem Fall nicht. Die Parameter sind als C-Strings spezifiziert und in C-Strings kommt NUL eben am Ende der Zeichenkette. Also der Fall, dass NUL zwischen drin kommt, gibt es nicht, weil die Zeichenkette beim ersten Auftauchen von NUL aufhört. Also kann man einen derartigen Fall nie begegnen. Deshalb fragte ich ja, wann du einen solchen Fall hast.

    Willst du Datenströme übergeben, dann musst du sie entweder aus einer Datei lesen oder über eine Pipe schicken.


Anmelden zum Antworten