【问题标题】:Is the bug in the grammar or in the code?是语法错误还是代码错误?
【发布时间】:2016-04-18 13:55:39
【问题描述】:

我不确定该语法对于也应该能够执行单引号和双引号的 shell 命令语言是否正确。似乎非平凡的命令可以工作,例如ls -al | sort | wc -l 但简单的方法不适用于单引号:echo 'foo bar' 不起作用。

%{
    #include "shellparser.h"
%}

%option reentrant
%option noyywrap

%x SINGLE_QUOTED
%x DOUBLE_QUOTED

%%

"|"                     { return PIPE; }

[ \t\r]                 { }
[\n]                    { return EOL; }

[a-zA-Z0-9_\.\-]+       { return FILENAME; }

[']                     { BEGIN(SINGLE_QUOTED); }
<SINGLE_QUOTED>[^']+    { }
<SINGLE_QUOTED>[']      { BEGIN(INITIAL); return ARGUMENT; }
<SINGLE_QUOTED><<EOF>>  { return -1; }

["]                     { BEGIN(DOUBLE_QUOTED); }
<DOUBLE_QUOTED>[^"]+    { }
<DOUBLE_QUOTED>["]      { BEGIN(INITIAL); return ARGUMENT; }
<DOUBLE_QUOTED><<EOF>>  { return -1; }

[^ \t\r\n|'"]+          { return ARGUMENT; }

%%

我的扫描和解析shell的代码是

 params[0] = NULL;
    printf("> ");
    i=1;
    do {
        lexCode = yylex(scanner);
        text = strdup(yyget_text(scanner));//yyget_text(scanner);
        /*printf("lexCode %d command %s inc:%d", lexCode, text, i);*/
        ca = text;
        if (lexCode != EOL) {
            params[i++] = text;
        }
        Parse(shellParser, lexCode, text);
        if (lexCode == EOL) {
            dump_argv("Before exec_arguments", i, params);
            exec_arguments(i, params);
            corpse_collector();
            Parse(shellParser, 0, NULL);
            i=1;
        }
    } while (lexCode > 0);

    if (-1 == lexCode) {
        fprintf(stderr, "The scanner encountered an error.\n");
    }

CMake 构建文件是

cmake_minimum_required(VERSION 3.0)
project(openshell)
find_package(FLEX)
FLEX_TARGET(ShellScanner shellscanner.l shellscanner.c)
set(CMAKE_VERBOSE_MAKEFILE on)
include_directories(/usr/include/readline)
ADD_EXECUTABLE(lemon lemon.c)
add_custom_command(OUTPUT shellparser.c COMMAND lemon -s shellparser.y DEPENDS shellparser.y)
add_executable(openshell shellparser.c ${FLEX_ShellScanner_OUTPUTS} main.c openshell.h errors.c errors.h util.c util.h stack.c stack.h shellscanner.l shellscanner.h)
file(GLOB SOURCES "./*.c")
target_link_libraries(openshell ${READLINE_LIBRARY} ${FLEX_LIBRARIES})
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -O3 -std=c99")

我的项目在my github 上可用。一个典型的 shell 会话,由于一些错误,只有一些命令可以工作,如下所示。

> ls -al | sort | wc
argument ::= FILENAME .
argumentList ::= argument .
command ::= FILENAME argumentList .
command ::= FILENAME .
command ::= FILENAME .
commandList ::= command .
commandList ::= command PIPE commandList .
commandList ::= command PIPE commandList .
 {(null)} {ls} {-al} {|} {sort} {|} {wc}
     45     398    2270
3874: child 3881 status 0x0000
in ::= in commandList EOL .
> who
command ::= FILENAME .
commandList ::= command .
 {(null)} {who}
dac      :0           2016-04-18 05:17 (:0)
dac      pts/2        2016-04-18 05:20 (:0)
3874: child 3887 status 0x0000
in ::= in commandList EOL .
> ls -al | awk '{print $1}'
argument ::= FILENAME .
argumentList ::= argument .
command ::= FILENAME argumentList .
argument ::= ARGUMENT .
argumentList ::= argument .
command ::= FILENAME argumentList .
commandList ::= command .
commandList ::= command PIPE commandList .
 {(null)} {ls} {-al} {|} {awk} {'}
awk: cmd. line:1: '
awk: cmd. line:1: ^ invalid char ''' in expression
3874: child 3896 status 0x0100
in ::= in commandList EOL .
> 

我可以观察到两个命令都有相同的错误:echo 'foo bar' 被乱码为{echo} {'},当我们希望它导致 {echo} {foo bar} 时,shell 会去掉引号并像这样执行命令

char *cmd[] = { "/usr/bin/echo", "foo bar", 0 };

【问题讨论】:

  • 看来你需要做一些调试才能发现。
  • @MartinJames 我正在调试第二个参数,假设外壳应该“去掉所有引号”,但我不能 100% 确定它应该。当 shell 程序如此普遍时,可用的 shell 语言语法却如此之少,我有点惊讶。
  • 正如我在代码审查中所说的那样,您处理引号的方法(您现在似乎试图在flex 中重现)对于 POSIX shell 根本不正确。不用猜测语法细节,consult the specifications.
  • @JohnBollinger 每种方法都错了吗? flex 不会使用 POSIX 标准编译是真的吗?
  • @Programmer400,不使用flex 是错误的。您指定的语法 via flex 与 POSIX shell 识别的语法不同,这就是您所说的要实现的语法。这甚至不仅仅是缺少功能的问题——您所做 实现的某些功能(例如双引号)的实现方式与 POSIX 指定的不同。这就是我将您引导至规范的原因。

标签: c shell posix flex-lexer lemon


【解决方案1】:

问题在于规则

&lt;SINGLE_QUOTED&gt;[^']+ { }

因为它会删除引号内的所有字符。作为“yytext”,你得到的只是结束引号(由于规则&lt;SINGLE_QUOTED&gt;['] ...)。您必须将文本存储在某处,并在检测到结束引号时使用它。例如。 (非常糟糕的编码风格,错误检查等省略,抱歉)

<SINGLE_QUOTED>[^']+    { mystring = strdup(yytext); }

<SINGLE_QUOTED>[']      { BEGIN(INITIAL);
      /*  mystring contains the whole string now,
           yytext contains only "'" */
                          return ARGUMENT; }

【讨论】:

    【解决方案2】:

    yytext 持有一个指向与最近识别的模式匹配的子字符串的指针。

    因此,当您的扫描器在单引号字符串的末尾返回 ARGUMENT 时,yytext 指向终止单引号。碰巧的是,这在您的调试跟踪中是可见的。

    如果你想“建立”一个令牌,你应该看看 flex 函数yymore()。 (并且不要忘记结束单引号不是引用字符串的一部分。)


    为单引号和双引号字符串返回ARGUMENT 既具有误导性又不精确。

    这是不精确的,因为双引号字符串与单引号字符串的处理方式非常不同,因为封闭的替换语法被扩展,需要递归调用解析器(甚至需要这样做才能识别字符串:以"$(echo "Hello, world!")" 为例)。

    这是具有误导性的,因为引用段的结尾并不标志着单词的结尾。事实上,一个头脑简单的扫描仪不会正确地找到字尾。考虑:

    x="a b"
    printf "[%s]\n" '$x'$x"$x"
    

    最后,我不清楚你为什么选择使用柠檬而不是野牛/yacc,因为你没有使用在这种情况下有用的一个特性:它实现了“推送”接口这一事实,允许您从词法分析器规则调用解析器。当然,现代野牛版本——甚至不那么现代的版本——也实现了这个功能。并不是说我对柠檬有任何偏见——我认为它可以很好地解决这个问题,正是因为需要进行递归解析。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-05-21
      • 1970-01-01
      • 1970-01-01
      • 2012-09-08
      • 2020-07-11
      • 2013-07-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多