注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

awk代码简单分析  

2013-06-02 23:30:48|  分类: GNU工具链源码分 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、awk工具
这个工具和grep、sed一样,应该算是unix系统下比较经典的文字处理铁三角组合,只是平时的时候处理的文本比较简单,所以对于awk的使用较好,而且对于它过于复杂的语法和奇异的格式也不是很喜欢。但是有时候使用grep和sed有无法满足一些基本的功能,例如说不同的field之间有一些数值关系的条件限制是,sed和grep作为纯粹的文本处理工具,它们显得无能为力,所以awk才有它存在的意义和市场。事实上,在大多数时候,如果只是简单的提取某一个特定格式文本行的特定field的话,我更愿意使用cut。为什么呢?这就涉及到一个问题,在使用信号灯实现互斥的时候,如果是严格的单临界互斥,我们更愿意使用互斥锁mutex而不是功能更加强大的semaphore。
二、主函数main大致流程
1、main函数本身
/* option processing. ready, set, go! */
    for (optopt = 0, old_optind = 1;  通常的命令行参数解析,包含了awk使用的各种命令行选项的解析和处理,例如-f指定语法文件,通过-v指定变量等价等
         (c = getopt_long(argc, argv, optlist, optab, NULL)) != EOF;
         optopt = 0, old_optind = optind) {
……
    }
out:
……
    /* Now process the pre-assignments */ 在执行命令之前,将命令行中通过-v指明的变量进行绑定,早于对awk语法文件的分析
    for (i = 0; i <= numassigns; i++)
        if (preassigns[i].stype == PRE_ASSIGN)
            (void) arg_assign(preassigns[i].val, TRUE);
        else    /* PRE_ASSIGN_FS */
            cmdline_fs(preassigns[i].val);
……
    /* No -f or --source options, use next arg */这里体现了对于命令行自带脚本内容和待处理文件的处理方法,如果没有通过-f指定语法文件,接下来的第一个(且仅一个)非选项参数作为语法文件
    if (numfiles == -1) {
        if (optind > argc - 1 || stopped_early) /* no args left or no program */
            usage(1, stderr);
        srcfiles_add(CMDLINE, argv[optind]);
        optind++;
    }
……
init_args(optind, argc, (char *) myname, argv)这个函数之后还需要使用,注意其中传入参数optind及为处理了命令行非选项参数之后剩余的内容,全部当作输入待处理文件
    /* Read in the program */ 根据语法文件进行语法分析及代码生成
    if (yyparse() != 0 || errcount != 0)
        exit(1);
……
    if (! exiting && (expression_value != NULL || end_block != NULL)) 逐个打开待处理文件
        do_input();
    if (end_block != NULL) {
        in_end_rule = TRUE;
        (void) interpret(end_block); 执行语法解释
    }
2、待处理文件解析
do_input()--->>while ((iop = nextfile(FALSE)) != NULL)---->>>for (; i < (long) (ARGC_node->lnode->numbr); i++)
而ARGC_node的初始化位于
static void
init_args(int argc0, int argc, char *argv0, char **argv)
    for (i = argc0, j = 1; i < argc; i++) {
        aptr = assoc_lookup(ARGV_node, tmp_number((AWKNUM) j), FALSE);
        *aptr = make_string(argv[i], strlen(argv[i]));
        (*aptr)->flags |= MAYBE_NUM;
        j++;
    }
    ARGC_node = install("ARGC",
            node(make_number((AWKNUM) j), Node_var, (NODE *) NULL));
即剩余的命令行非选项参数全部作为待处理文本文件。
3、命令行变量赋值
    /* Now process the pre-assignments */
    for (i = 0; i <= numassigns; i++)
        if (preassigns[i].stype == PRE_ASSIGN)
            (void) arg_assign(preassigns[i].val, TRUE);
        else    /* PRE_ASSIGN_FS */
            cmdline_fs(preassigns[i].val);

arg_assign(char *arg, int initing)

    cp = strchr(arg, '=');
……
{
        /*
         * Recent versions of nawk expand escapes inside assignments.
         * This makes sense, so we do it too.
         */
        it = make_str_node(cp, strlen(cp), SCAN);创建一个字符串节点,它的内容即为等号右边的字符串内容,也就是说新定义的变量的数值为一个字符串类型
        it->flags |= MAYBE_NUM;
#ifdef LC_NUMERIC
        setlocale(LC_NUMERIC, "C");
        (void) force_number(it);
        setlocale(LC_NUMERIC, "");
#endif /* LC_NUMERIC */
        var = variable(arg, FALSE, Node_var);以等号左边内容作为变量名,类型为变量
        lhs = get_lhs(var, &after_assign, FALSE);
        unref(*lhs);
        *lhs = it;将变量名和对应的数值联系起来
        if (after_assign != NULL)
            (*after_assign)();
    }
在之后的代码也可以看到,这是对于C语言中 左值和右值 的模拟,例如对于一个变量,var只是表示它的名字,而val才是它的内容,这也是awk和bash语法的一个重要差别。在bash中,所有的字面常量都是作为字面常量本身,如果要求变量对应的值,则需要通过$字符来主动要求bash来进行展开,而在awk中,变量默认都会有一个左值和右值,从而根据变量名称本身获得变量的内容,这个展开是隐形的。
三、一些语法的细节
1、命令中match 和 action的识别
awkgram.y为awk的语法定义文件
rule
    : pattern action
      {
        $1->rnode = $2;
      }
    | pattern statement_term

其中,看一下action的定义
pattern
    : /* empty */
      {
        $$ = append_pattern(&expression_value, (NODE *) NULL);
      }
    | exp
      {
        $$ = append_pattern(&expression_value, $1);
      }
    | exp ',' exp
      {
        NODE *r;

        getnode(r);
        r->type = Node_line_range;
        r->condpair = node($1, Node_cond_pair, $3);
        r->triggered = FALSE;
        $$ = append_pattern(&expression_value, r);
      }
    | LEX_BEGIN
      {
        begin_or_end_rule = TRUE;
        $$ = append_pattern(&begin_block, (NODE *) NULL);
      }
    | LEX_END
      {
        begin_or_end_rule = parsing_end_rule = TRUE;
        $$ = append_pattern(&end_block, (NODE *) NULL);
      }
    ;

action
    : l_brace statements r_brace opt_semi opt_nls
        { $$ = $2; }
    ;
可以看到,pattern的格式比较复杂,包含了所有的表达式文件,而action的最大特征就是开始是一个l_brace,也就是一个左大括号,然后以一个右大括号结束,之间包含了各种指令内容。
2、$引用的意义
non_post_simp_exp
    | '$' non_post_simp_exp
        { $$ = node($2, Node_field_spec, (NODE *) NULL); }
    ;
……
non_post_simp_exp
    : '!' simp_exp %prec UNARY
        { $$ = node($2, Node_not, (NODE *) NULL); }
    | '(' exp r_paren
        { $$ = $2; }
    | LEX_BUILTIN
      '(' opt_expression_list r_paren
        { $$ = snode($3, Node_builtin, (int) $1); }

可以看到,$之后可以跟的语法结构是相当丰富的,但是最后都需要经过一个特殊的field_spec结构进行求值,将其转换为对于不同域的求值。
r_get_lhs(register NODE *ptr, Func_ptr *assign, int reference)
case Node_field_spec:
        {
        int field_num;

        n = tree_eval(ptr->lnode);对于$后的表达式求值,如果之后是一个变量,则其值即为变量的右值,也就是其内容
        if (do_lint) {
            if ((n->flags & NUMBER) == 0) {
                lintwarn(_("attempt to field reference from non-numeric value"));
                if (n->stlen == 0)
                    lintwarn(_("attempt to reference from null string"));
            }
        }
        field_num = (int) force_number(n);将该值强制转换为数值,所以如果说 命令行-vkeyfield=5,则$keyfield首先取出keyfield的值为字符串的5,返回给n,然后通过force_number将该值转换为数值,并在接下来转换为特定域的取值。
3、字符串的识别
yylex(void)
case '"':
    string:
        esc_seen = FALSE;
        while ((c = nextc()) != '"') {
……
}
        yylval.nodeval = make_str_node(tokstart,
                    tok - tokstart, esc_seen ? SCAN : 0);
        yylval.nodeval->flags |= PERM;
        if (intlstr) {
            yylval.nodeval->flags |= INTLSTR;
            intlstr = FALSE;
            if (do_intl)
                dumpintlstr(yylval.nodeval->stptr,
                        yylval.nodeval->stlen);
         }
        return lasttok = YSTRING;
这个make_str_node和之前命令行-v中使用的字符串生成类似,就是生成一个str类型的变量,其类型为数值类型r->type = Node_val;设置了r->flags = (STRING|STRCUR|MALLOC);标志。
4、字面常量识别
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
……
            yylval.nodeval = make_number(atof(tokstart));
        yylval.nodeval->flags |= PERM;
        return lasttok = YNUMBER;

#define    make_number(x)    mk_number((x), (unsigned int)(MALLOC|NUMCUR|NUMBER))
5、字面变量
    tokadd('\0');
    emalloc(tokkey, char *, tok - tokstart, "yylex");
    memcpy(tokkey, tokstart, tok - tokstart);
……
    yylval.sval = tokkey; 返回一个字符串,返回类型为NAME,这个字面标识符的解释在不同的语法上下文中有不同的解释
    if (*lexptr == '(')
        return lasttok = FUNC_CALL;
    else {
        static short goto_warned = FALSE;

#define SMART_ALECK    1
        if (SMART_ALECK && do_lint
            && ! goto_warned && strcasecmp(tokkey, "goto") == 0) {
            goto_warned = TRUE;
            lintwarn(_("`goto' considered harmful!\n"));
        }
        return lasttok = NAME;
    }
6、字面量的常见处理方式
解析时
variable
    : NAME
        { $$ = variable($1, CAN_FREE, Node_var_new); } 也就是默认作为一个变量类型来处理
    | NAME '[' expression_list ']'
      {
        NODE *n;

        if ((n = lookup($1)) != NULL && ! isarray(n))
            yyerror(_("use of non-array as array"));
        else if ($3 == NULL) {
            fatal(_("invalid subscript expression"));
        } else if ($3->rnode == NULL) {
            $$ = node(variable($1, CAN_FREE, Node_var_array), Node_subscript, $3->lnode);
            freenode($3);
        } else
            $$ = node(variable($1, CAN_FREE, Node_var_array), Node_subscript, $3);
      }
    | '$' non_post_simp_exp
        { $$ = node($2, Node_field_spec, (NODE *) NULL); }
    ;
引用时
    case Node_var_new:
        ptr->type = Node_var;
        ptr->var_value = Nnull_string;
        /* fall through */
    case Node_var:
        if (do_lint && reference && var_uninitialized(ptr))
            lintwarn(_("reference to uninitialized variable `%s'"),
                          ptr->vname);

        aptr = &(ptr->var_value); 取得是变量的内容
#ifdef GAWKDEBUG
        if (ptr->var_value->stref <= 0)
            cant_happen();
#endif
        break;
7、变量查找及hash
/* variable --- make sure NAME is in the symbol table */

NODE *
variable(char *name, int can_free, NODETYPE type)
{
    register NODE *r;

    if ((r = lookup(name)) != NULL) {
        if (r->type == Node_func)
            fatal(_("function `%s' called with space between name and `(',\n%s"),
                r->vname,
                _("or used as a variable or an array"));
    } else {
        /* not found */
        if (! do_traditional && STREQ(name, "PROCINFO"))
            r = load_procinfo();
        else if (STREQ(name, "ENVIRON"))
            r = load_environ();
        else {
            /*
             * This is the only case in which we may not free the string.
             */
            NODE *n;

            if (type == Node_var)
                n = node(Nnull_string, type, (NODE *) NULL);
            else
                n = node((NODE *) NULL, type, (NODE *) NULL);

            return install(name, n);
        }
    }
    if (can_free)
        free(name);
    return r;
}
variable函数就是识别出一个字面标志符的时候调用的函数,传入的第二个参数CAN_FREE为true,所以在执行ariable函数时,首先从hash表中查找变量,如果找到则返回该值,并且释放传入指针,否则执行node函数创建一个节点,最后执行install将新创建的node安装到hash表中,下次查找的时候就可以找到。
7、1 查找函数
NODE *
lookup(const char *name)
{
    register NODE *bucket;
    register size_t len;

    len = strlen(name);
    for (bucket = variables[hash(name, len, (unsigned long) HASHSIZE)]; 其中variables为一个全局变量
            bucket != NULL; bucket = bucket->hnext)
        if (bucket->hlength == len && STREQN(bucket->hname, name, len))
            return bucket->hvalue;

    return NULL;
}
7、2 安装函数
NODE *
install(char *name, NODE *value)
{
    register NODE *hp;
    register size_t len;
    register int bucket;

    var_count++;
    len = strlen(name);
    bucket = hash(name, len, (unsigned long) HASHSIZE);
    getnode(hp);
    hp->type = Node_hashnode;
    hp->hnext = variables[bucket];
    variables[bucket] = hp
;
    hp->hlength = len;
    hp->hvalue = value;
    hp->hname = name;
    hp->hvalue->vname = name;
    return hp->hvalue;
}
8、常见的比较运算符实现
eval.c
NODE *
r_tree_eval(register NODE *tree, int iscond)
case Node_equal:
        di = cmp_nodes(t1, t2);
        unref(t1);
        unref(t2);
        switch (tree->type) {
        case Node_equal:
            return tmp_number((AWKNUM) (di == 0));

真正比较函数实现
int
cmp_nodes(register NODE *t1, register NODE *t2)
{
    register int ret;
    register size_t len1, len2;
    register int l;
    int ldiff;

    if (t1 == t2)
        return 0;
    if (t1->flags & MAYBE_NUM)
        (void) force_number(t1);
    if (t2->flags & MAYBE_NUM)
        (void) force_number(t2); 如果是两者中有任何一个可能为数值类型,则优先转换为数值类型
    if ((t1->flags & NUMBER) && (t2->flags & NUMBER)) { 如果两者都为数值类型,则进行数值比较,否则进行字符串比较
        if (t1->numbr == t2->numbr)
            return 0;
        /* don't subtract, in case one or both are infinite */
        else if (t1->numbr < t2->numbr)
            return -1;
        else
            return 1;
    }
    (void) force_string(t1);
    (void) force_string(t2);
    len1 = t1->stlen;
    len2 = t2->stlen;
    ldiff = len1 - len2;
    if (len1 == 0 || len2 == 0)
        return ldiff;
    l = (ldiff <= 0 ? len1 : len2);
    if (IGNORECASE) {
        const unsigned char *cp1 = (const unsigned char *) t1->stptr;
        const unsigned char *cp2 = (const unsigned char *) t2->stptr;

#ifdef MBS_SUPPORT
        if (gawk_mb_cur_max > 1) {
            mbstate_t mbs;
            memset(&mbs, 0, sizeof(mbstate_t));
            ret = strncasecmpmbs((const char *) cp1, mbs,
                         (const char *) cp2, mbs, l);
        } else
#endif
        for (ret = 0; l-- > 0 && ret == 0; cp1++, cp2++)
            ret = casetable[*cp1] - casetable[*cp2];
    } else
        ret = memcmp(t1->stptr, t2->stptr, l);
    return (ret == 0 ? ldiff : ret);
}
  评论这张
 
阅读(1336)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017