execvp() 打印无效选项 -- '

execvp() prints invalid option -- '

我正在创建简单的 shell 解释器。我正在使用一个扫描器和一个使用 lex 和 yacc 实用程序的解析器来创建这个 shell。当我给命令一个参数时出现问题,因为它 returns 无效 option.For 例如当我输入 ls -l 它 returns ls: invalid option -- ' 即使我已经检查了arg_list 的值,它保存参数和命令的值并存储正确的参数。

谁能帮我理解为什么会出现此错误?对于我的代码,只要字符串匹配,lex 就会扫描输入并 returns 向解析器发送一个标记。

(这段程序代码只能运行一个参数)。

这是我的 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()。 "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    {$$=;insertArg(,len);count++;}
 |
 ;
C:WORD {$$=;insertComm(,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]='[=11=]';
    printf("\n arg_list[0]= %s",arg_list[0]);
    for(i=0;arg_list[i]!='[=11=]';i++)
    {
        printf("\n arg_list[%d]= %s",i,arg_list[i]);
    }
    //printf("%s",sizeof(arg_list[1]));
    execvp(arg_list[0],arg_list);
    }
}

诊断

您的代码最终在参数中的 -l 之后包含换行符,并且 ls 抱怨它没有换行符作为有效的选项字母。

诊断技术

我修改了你的 main 中的打印代码为:

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 之后和 ] 之前有一个换行符,它标志着字符串的结尾。

代码中有各种更改需要注意。首先,打印格式字符串 end 带有换行符,而不是开始——这有助于确保及时输出输出。如果末尾没有换行符,输出可能会无限期地延迟,直到某些东西确实产生了换行符。

其次,我的编译器警告我:

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]!='[=12=]';i++)
                        ^~
shellg.y:60:13: note: did you mean to dereference the pointer?
     for(i=0;arg_list[i]!='[=12=]';i++)
             ^

(旁白:我将文件命名为 shell.lshellg.y — 两次使用相同的基本名称让我不知所措,因为在我的正常构建机制下这两个目标文件都是 shell.o .我猜你使用不同的规则来编译你的代码。)

我将 '[=33=]'(这是一个 "null pointer constant",但它是一个常规的,通常表示作者感到困惑)更改为 NULL。

循环中的打印格式很重要;请注意我是如何将 %s 包含在标记字符 [%s] 中的。 ] 在输出中的位置立即表明换行符是问题所在。这是一项有价值的技术;它使不可见的再次可见。

最后的 fflush(stdout) 在此上下文中并不是真正重要的,但它确实确保在 execvp() 替换程序并永远丢失该输出之前生成任何未决的标准输出。使用 fflush(0)fflush(NULL) 以确保其他文件流(标准错误)也被完全刷新是合理的,但标准错误通常不会缓冲太多。

处方

显然,解决方法是升级词法代码以不在参数中包含换行符。然而,为什么会这样还不是很明显。我更改了语法以进行一些打印:

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

%%

请特别注意 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: 
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个函数声明,但其中none个是函数原型。您需要在不需要参数的地方添加预期参数或 void

还有其他问题。使用我的正常编译选项(rmkmake 的变体;-u 表示 'unconditional rebuild'),我得到:

$ 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: 
    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.
$

我也懒得解决所有这些问题。

我认为您也需要修复词法分析器。白色的space应该不包含在返回的token中,但是好像是加在最后的,不过不太确定how/why.

$ ./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 打印让我很困惑。 How/why是相对于之前的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
$

祝您跟踪愉快 how/why R2B 打印输出包括换行符。

详细追踪——打印地址

JFTR,我正在开发 Mac 运行ning 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, , ); $$=;insertArg(,len);count++;}
 |  /*Nothing */
                {printf("R1B: - nothing\n");}
 ;

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

%%

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 中的指针如何指向单个字符串中的不同位置。词法分析器在 returns 时在标记后插入一个空值,但用空白或换行符(如果我键入任何内容,则用制表符)替换该空值。这说明了为什么需要复制令牌。 What's "in" arg_list[1] 随着词法分析的进行而变化。这就是出现换行符的原因。

注意评论

请注意 rici 的评论并关注 link: