Диагностика
Ваш код заканчивается включением новой строки после -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 и перейдите по ссылке тоже: