execvp()打印无效选项 - '

时间:2018-04-29 13:41:36

标签: c shell yacc lex execvp

我正在创建简单的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);
    }
}

1 个答案:

答案 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.lshellg.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,其中不需要参数。

还有其他问题。使用我的常规编译选项(rmkmake的变体; -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的评论,并点击链接: