execvp () выводит неверную опцию - ' - PullRequest
0 голосов
/ 29 апреля 2018

Я создаю простой интерпретатор оболочки. Я создаю эту оболочку, используя сканер и парсер, используя утилиты lex и yacc. Проблема возникает, когда я даю аргумент команде, поскольку она возвращает недопустимую опцию. Например, когда я набираю ls -l, она возвращает ls: invalid option -- ', даже если я проверил значение аргумента arg_list, который содержит значение аргументов, команды и хранит правильный аргумент.

Может кто-нибудь, пожалуйста, помогите мне понять, почему я получаю эту ошибку? Для моего кода lex сканирует входные данные и возвращает токен анализатору при совпадении строки.

(Этот код программы может запускать только один аргумент).

Это мой файл спецификации lex. "Shell.l"

%{
    #include<stdio.h>
    #include<stdlib.h>
    #include"y.tab.h"
    extern int len;
%}
%option noyywrap
letter [a-zA-Z]+

%%
(\-)?{letter}                       {yylval.id=yytext;len=yyleng;return WORD;}
[ \t\n]                         {;}
.                           {return NOTOKEN;}

%%

Это мой файл спецификации yacc, а также my main (). "Shell.y"

%{
    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h>
    extern int yylex();
    void yyerror();
    int append =0;
    void insertArg();
    void insertComm();
    char *arg_list[20];
    int i,count=1;
    int len;
    char command[20];
%}
%union{
    float f;
    char  *id;
}
%start C
%token <id> WORD
%token  NOTOKEN
%type <id> C A

%%
A:A WORD    {$$=$2;insertArg($2,len);count++;}
 |
 ;
C:WORD {$$=$1;insertComm($1,len);/*printf("%s\n",$$);*/} A  
 ;

%%

void insertComm(char *c,int len)
{   

    for(i=0;i<len;i++)
    {   
        command[i]=c[i];

    }
    arg_list[0]=&command[0];
}
void insertArg(char *arg,int len)
{
    arg_list[count]=&arg[0];
}
void yyerror(char *msg){
    fprintf(stderr,"%s\n",msg);
    exit(1);
}

int main()
{   
    int status;
    while(1){
    yyparse();
    //arg_list[count]='\0';
    printf("\n arg_list[0]= %s",arg_list[0]);
    for(i=0;arg_list[i]!='\0';i++)
    {
        printf("\n arg_list[%d]= %s",i,arg_list[i]);
    }
    //printf("%s",sizeof(arg_list[1]));
    execvp(arg_list[0],arg_list);
    }
}

1 Ответ

0 голосов
/ 29 апреля 2018

Диагностика

Ваш код заканчивается включением новой строки после -l в аргументе, и ls жалуется на то, что в нем нет символа новой строки в качестве допустимой буквы параметра.

Диагностическая техника

Я изменил код печати в вашей главной книге, чтобы он читался так:

printf("arg_list[0]= %s\n", arg_list[0]);
for (i = 0; arg_list[i] != NULL; i++)
{
    printf("arg_list[%d] = [%s]\n", i, arg_list[i]);
}
fflush(stdout);

Произведено:

$ ./shell
ls -l
arg_list[0]= ls
arg_list[0] = [ls]
arg_list[1] = [-l
]
ls: illegal option -- 

usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
$

Обратите внимание, что после -l и до ] стоит новая строка, обозначающая конец строки.

В коде есть различные изменения, которые нужно отметить. Во-первых, строки формата печати заканчиваются новой строкой, а не началом - это помогает обеспечить своевременную выдачу вывода. Без новой строки в конце вывод может быть отложен на неопределенное время, пока что-то не произведет новую строку.

Во-вторых, мой компилятор предупредил меня о:

shellg.y: In function ‘main’:
shellg.y:60:24: warning: comparison between pointer and zero character constant [-Wpointer-compare]
     for(i=0;arg_list[i]!='\0';i++)
                        ^~
shellg.y:60:13: note: did you mean to dereference the pointer?
     for(i=0;arg_list[i]!='\0';i++)
             ^

(Помимо: я назвал файлы shell.l и shellg.y - использование одного и того же базового имени дважды оставляет меня в растерянности, потому что оба объектных файла будут shell.o при моем обычном режиме сборки. Я полагаю, вы используете разные правила для компилируем ваш код.)

Я изменил '\0' (который является «константой нулевого указателя», но является обычным и, как правило, указывает, что писатель запутался) в NULL.

Формат печати в цикле важен; обратите внимание, как я заключил %s в маркерные символы [%s]. Размещение ] в выходных данных сразу показывает, что новая строка является проблемой. Это ценная техника; это делает невидимое снова видимым.

Окончательный fflush(stdout) на самом деле не имеет решающего значения в этом контексте, но он гарантирует, что любой ожидающий стандартный вывод был сгенерирован до того, как execvp() заменит программу и потеряет этот вывод навсегда. Было бы разумно использовать fflush(0) или fflush(NULL), чтобы гарантировать, что другие файловые потоки (стандартная ошибка) также полностью сбрасываются, но стандартная ошибка обычно не очень буферизуется.

Prescription

Очевидно, что исправление заключается в обновлении лексического кода, чтобы он не включал символ новой строки в аргумент. Однако не сразу понятно, почему это происходит вообще. Я изменил грамматику для печати:

%%
A:A WORD    {printf("R1: len %d [%s]\n", len, $2); $$=$2;insertArg($2,len);count++;}
 |
 ;
C:WORD {printf("R2A: len %d [%s]\n", len, $1); $$=$1;insertComm($1,len);/*printf("%s\n",$$);*/} A  
    {printf("R2B: len %d [%s]\n", len, $3);}
 ;

%%

Обратите внимание, в частности, на строку R2B; там может быть действие, но у вас его не было.

При запуске я получаю:

$ ./shell
ls -l
R2A: len 2 [ls]
R1: len 2 [-l]
R2B: len 2 [-l
]
arg_list[0]= ls
arg_list[0] = [ls]
arg_list[1] = [-l
]
ls: illegal option -- 

usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
$

Интересно, что -l отображается в выводе R1 без перевода строки, но к моменту вывода R2B добавляется перевод строки. Тот было неожиданно, но случайно. Я добавил печать, чтобы убедиться, что освещение было завершено; Я очень рад, что сделал!

Итак, что дает? Почему токен не сбрасывается? Почему добавлена ​​новая строка? Почему не создается копия токена вместо хранения указателя на yytext?

Долгосрочным решением будет сделать копию; так что мы можем начать прямо. Я предполагаю, что strdup() доступен для вас и будет использовать это. Я добавил #include <string.h> во включенные и использовал:

void insertArg(char *arg,int len)
{
    arg_list[count] = strdup(arg);
}

Привет, Престо! Желаемый вывод:

$ ./shell
ls -l
R2A: len 2 [ls]
R1: len 2 [-l]
R2B: len 2 [-l
]
arg_list[0]= ls
arg_list[0] = [ls]
arg_list[1] = [-l]
total 128
-rw-r--r--  1 jleffler  staff   1443 Apr 29 08:36 data
-rwxr-xr-x  1 jleffler  staff  24516 Apr 29 09:08 shell
-rw-r--r--  1 jleffler  staff    297 Apr 29 08:36 shell.l
-rw-r--r--  1 jleffler  staff  13568 Apr 29 08:38 shell.o
-rw-r--r--  1 jleffler  staff   4680 Apr 29 09:08 shellg.o
-rw-r--r--  1 jleffler  staff   1306 Apr 29 09:08 shellg.y
-rw-r--r--  1 jleffler  staff   2245 Apr 29 09:08 y.tab.h
$

Дальнейшие наблюдения

Вам необходимо убедиться, что вы освободили дублированные строки. Вы также должны скомпилировать с параметрами предупреждения. Вопреки моей обычной практике, я собрал только предупреждения по умолчанию (почти нет). Составление грамматических шоу:

yacc -d shellg.y
shellg.y:28.3: warning: empty rule for typed nonterminal, and no action
shellg.y:30.3-31.44: warning: unused value: $2
gcc -O -c y.tab.c
mv y.tab.o shellg.o
rm -f y.tab.c

Вы должны решить оба этих вопроса. Вы не объявляете прототипы для функций, которые вы определяете - у вас есть:

extern int yylex();
void yyerror();
int append =0;
void insertArg();
void insertComm();

Существует 4 объявления функций, но ни одно из них не является прототипом функции. Вам нужно добавить ожидаемые аргументы или void, если аргументы не ожидаются.

Есть и другие проблемы. С моими обычными параметрами компиляции (rmk - это вариант make; -u означает «безусловное перестроение»), я получаю:

$ rmk -ku
    yacc  shellg.y
shellg.y:28.3: warning: empty rule for typed nonterminal, and no action
shellg.y:30.3-31.44: warning: unused value: $2
    gcc -O3   -g         -std=c11   -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes         -c y.tab.c
shellg.y:7:5: error: function declaration isn’t a prototype [-Werror=strict-prototypes]
     extern int yylex();
     ^~~~~~
shellg.y:8:5: error: function declaration isn’t a prototype [-Werror=strict-prototypes]
     void yyerror();
     ^~~~
shellg.y:10:5: error: function declaration isn’t a prototype [-Werror=strict-prototypes]
     void insertArg();
     ^~~~
shellg.y:11:5: error: function declaration isn’t a prototype [-Werror=strict-prototypes]
     void insertComm();
     ^~~~
shellg.y:36:6: error: no previous prototype for ‘insertComm’ [-Werror=missing-prototypes]
 void insertComm(char *c,int len)
      ^~~~~~~~~~
shellg.y:45:6: error: no previous prototype for ‘insertArg’ [-Werror=missing-prototypes]
 void insertArg(char *arg,int len)
      ^~~~~~~~~
shellg.y: In function ‘insertArg’:
shellg.y:45:30: error: unused parameter ‘len’ [-Werror=unused-parameter]
 void insertArg(char *arg,int len)
                              ^~~
shellg.y: At top level:
shellg.y:50:6: error: no previous prototype for ‘yyerror’ [-Werror=missing-prototypes]
 void yyerror(char *msg){
      ^~~~~~~
shellg.y: In function ‘main’:
shellg.y:57:9: error: unused variable ‘status’ [-Werror=unused-variable]
     int status;
         ^~~~~~
cc1: all warnings being treated as errors
rmk: error code 1
    lex  shell.l
    gcc -O3   -g         -std=c11   -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes         -c lex.yy.c
lex.yy.c:1119:16: error: ‘input’ defined but not used [-Werror=unused-function]
     static int input  (void)
                ^~~~~
lex.yy.c:1078:17: error: ‘yyunput’ defined but not used [-Werror=unused-function]
     static void yyunput (int c, register char * yy_bp )
                 ^~~~~~~
cc1: all warnings being treated as errors
rmk: error code 1
'shell' not remade because of errors.
'all' not remade because of errors.
$

Я был слишком ленив, чтобы исправить все эти проблемы.

Я думаю, вам тоже нужно починить свой лексический анализатор. Пробел не должен быть включен в возвращаемые токены, но, кажется, он добавлен в конец, но я не совсем уверен, как / почему.

$ ./shell
ls -l abelone ducks
R2A: len 2 [ls]
R1: len 2 [-l]
R1: len 7 [abelone]
R1: len 5 [ducks]
R2B: len 5 [ducks
]
arg_list[0]= ls
arg_list[0] = [ls]
arg_list[1] = [-l]
arg_list[2] = [abelone]
arg_list[3] = [ducks]
ls: abelone: No such file or directory
ls: ducks: No such file or directory
$

Эта R2B печать озадачивает меня. Как / почему это модифицируется по сравнению с R1, перед которым отображается ducks и без перевода строки. Я думаю, тебе придется это отследить.

Добавление диагностики в анализатор:

%%
(\-)?{letter}        {printf("L1: [%s]\n", yytext); yylval.id=yytext;len=yyleng;return WORD;}
[ \t\n]              {printf("L2: [%s]\n", yytext);}
.                    {printf("L3: [%s]\n", yytext); return NOTOKEN;}
%%

и его запуск дает:

$ ./shell
ls -l
L1: [ls]
R2A: len 2 [ls]
L2: [ ]
L1: [-l]
R1: len 2 [-l]
L2: [
]
R2B: len 2 [-l
]
arg_list[0]= ls
arg_list[0] = [ls]
arg_list[1] = [-l]
total 224
-rw-r--r--  1 jleffler  staff   1443 Apr 29 08:36 data
-rw-r--r--  1 jleffler  staff    303 Apr 29 09:16 makefile
-rwxr-xr-x  1 jleffler  staff  24516 Apr 29 09:29 shell
-rw-r--r--  1 jleffler  staff    385 Apr 29 09:29 shell.l
-rw-r--r--  1 jleffler  staff  13812 Apr 29 09:29 shell.o
-rw-r--r--  1 jleffler  staff   4680 Apr 29 09:08 shellg.o
-rw-r--r--  1 jleffler  staff   1306 Apr 29 09:08 shellg.y
-rw-r--r--  1 jleffler  staff  41299 Apr 29 09:16 y.tab.c
-rw-r--r--  1 jleffler  staff   2245 Apr 29 09:08 y.tab.h
$

Получайте удовольствие, отслеживая, как / почему распечатка R2B включает перевод строки.

Детальное отслеживание - печать адресов

JFTR , я работаю на Mac под управлением MacOS 10.13.4 High Sierra, с GCC 7.3.0 (самодельный), с Bison 2.3 под Yacc и Flex 2.5.35 Apple (flex-31) работает как Lex.

Вот очищенный shell.l - он корректно компилируется в моем строгом режиме предупреждений:

%{
    #include <stdio.h>
    #include <stdlib.h>
    #include "y.tab.h"
    extern int len;
%}
%option noyywrap
%option noinput
%option nounput
letter [a-zA-Z]+

%%
(\-)?{letter}       {printf("L1: [%s]\n", yytext); yylval.id=yytext;len=yyleng;return WORD;}
[ \t\n]             {printf("L2: [%s]\n", yytext);}
.                   {printf("L3: [%s]\n", yytext); return NOTOKEN;}
%%

А вот более тяжелая диагностическая версия shellg.y (которая также аккуратно компилируется в моем строгом режиме предупреждений). Я восстановил исходный код в insertArg(), который копирует адрес в массив arg_list, но я также добавил код для распечатки полного содержимого arg_list при каждом вызове. Это оказывается информативным!

%{
    #include<stdio.h>
    #include<stdlib.h>
    #include <string.h>
    #include<unistd.h>
    extern int yylex(void);
    void yyerror(char *msg);
    int append =0;
    void insertArg(char *arg, int len);
    void insertComm(char *arg, int len);
    char *arg_list[20];
    int i,count=1;
    int len;
    char command[20];
%}
%union{
    float f;
    char  *id;
}
%start C
%token <id> WORD
%token  NOTOKEN
%type <id> C A

%%
A:  A   WORD    {printf("R1A: len %d %p [%s]\n", len, $2, $2); $$=$2;insertArg($2,len);count++;}
 |  /*Nothing */
                {printf("R1B: - nothing\n");}
 ;

C:  WORD
    {printf("R2A: len %d %p [%s]\n", len, $1, $1); $$=$1;insertComm($1,len);}
    A  
    {printf("R2B: len %d %p [%s]\n", len, $3, $3);}
 ;

%%

void insertComm(char *c, int len)
{
    printf("Command: %d [%s]\n", len, c);
    for (i = 0; i < len; i++)
    {
        command[i] = c[i];
    }
    arg_list[0] = &command[0];
}

void insertArg(char *arg, int len)
{
    printf("Argument: %d [%s]\n", len, arg);
    //arg_list[count] = strdup(arg);
    arg_list[count] = arg;
    for (int i = 0; i < count; i++)
        printf("list[%d] = %p [%s]\n", i, arg_list[i], arg_list[i]);
}

void yyerror(char *msg)
{
    fprintf(stderr, "%s\n", msg);
    exit(1);
}

int main(void)
{
    while (1)
    {
        yyparse();
        printf("arg_list[0]= %s\n", arg_list[0]);
        for (i = 0; arg_list[i] != NULL; i++)
        {
            printf("arg_list[%d] = [%s]\n", i, arg_list[i]);
        }
        fflush(stdout);
        execvp(arg_list[0], arg_list);
    }
}

При компиляции и запуске я могу получить вывод:

$ ./shell
ls -l -rt makefile data shell
L1: [ls]
R2A: len 2 0x7f9dd4801000 [ls]
Command: 2 [ls]
R1B: - nothing
L2: [ ]
L1: [-l]
R1A: len 2 0x7f9dd4801003 [-l]
Argument: 2 [-l]
list[0] = 0x10ace8180 [ls]
L2: [ ]
L1: [-rt]
R1A: len 3 0x7f9dd4801006 [-rt]
Argument: 3 [-rt]
list[0] = 0x10ace8180 [ls]
list[1] = 0x7f9dd4801003 [-l -rt]
L2: [ ]
L1: [makefile]
R1A: len 8 0x7f9dd480100a [makefile]
Argument: 8 [makefile]
list[0] = 0x10ace8180 [ls]
list[1] = 0x7f9dd4801003 [-l -rt makefile]
list[2] = 0x7f9dd4801006 [-rt makefile]
L2: [ ]
L1: [data]
R1A: len 4 0x7f9dd4801013 [data]
Argument: 4 [data]
list[0] = 0x10ace8180 [ls]
list[1] = 0x7f9dd4801003 [-l -rt makefile data]
list[2] = 0x7f9dd4801006 [-rt makefile data]
list[3] = 0x7f9dd480100a [makefile data]
L2: [ ]
L1: [shell]
R1A: len 5 0x7f9dd4801018 [shell]
Argument: 5 [shell]
list[0] = 0x10ace8180 [ls]
list[1] = 0x7f9dd4801003 [-l -rt makefile data shell]
list[2] = 0x7f9dd4801006 [-rt makefile data shell]
list[3] = 0x7f9dd480100a [makefile data shell]
list[4] = 0x7f9dd4801013 [data shell]
L2: [
]
R2B: len 5 0x7f9dd4801018 [shell
]
arg_list[0]= ls
arg_list[0] = [ls]
arg_list[1] = [-l -rt makefile data shell
]
arg_list[2] = [-rt makefile data shell
]
arg_list[3] = [makefile data shell
]
arg_list[4] = [data shell
]
arg_list[5] = [shell
]
ls: illegal option --  
usage: ls [-ABCFGHLOPRSTUWabcdefghiklmnopqrstuwx1] [file ...]
$

Обратите внимание, как указатели в arg_list указывают на разные позиции в одной строке. Лексический анализатор вставляет нулевое значение после токена, когда он возвращается, но заменяет его пустым или символом новой строки (или табуляции, если бы я их набрал). Это показывает, почему необходимо копировать токены. То, что «в» arg_list[1] меняется по мере продолжения лексического анализа. Вот почему появляется новая строка.

Обратите внимание на комментарии

Обратите внимание на комментарии rici и перейдите по ссылке тоже:

...