我正在创建简单的shell解释器。我正在使用扫描程序和使用lex和yacc实用程序的解析器创建此shell。当我给命令一个参数返回无效选项时出现问题。例如,当我输入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规范文件和我的main()。 &#34; shell.y&#34;
%{
#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);
}
}
答案 0 :(得分:4)
您的代码最终会在参数-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'
(这是一个&#34;空指针常量&#34;,但它是一个常规的,通常表示编写者感到困惑)更改为NULL。
循环中的打印格式很重要;注意我如何将%s
括在标记字符[%s]
中。 ]
在输出中的位置会立即显示换行符是问题所在。这是一项有价值的技术;它使隐形再次可见。
最终fflush(stdout)
在这种情况下并不是真正重要,但它确实在execvp()
替换程序之前生成了任何未决的标准输出,并永远丢失了该输出。使用fflush(0)
或fflush(NULL)
来确保其他文件流(标准错误)也被完全刷新是合理的,但标准错误通常不会被非常缓冲。
显然,修复方法是将词法代码升级为不在参数中包含换行符。但是,为什么会发生这种情况并不是很明显。我改变了语法来做一些打印:
%%
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
表示无条件重建&#39;),我得到:
$ 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
中的指针如何指向单个字符串中的不同位置。词法分析器在返回时在令牌之后插入一个null,但用空格或换行符替换该null(如果我输入任何一个,则替换为tab)。这表明为什么有必要复制令牌。什么&#34;&#34;&#34;随着词法分析的进行,arg_list[1]
会发生变化。这就是换行出现的原因。
请注意rici的评论,并点击链接: