在AWK中,为什么像$(NF + 1)这样不存在的字段不等于零?

时间:2018-08-01 12:09:00

标签: awk

使用AWK时,我很难理解为什么不存在的字段($NF之后的字段)不等于零。

在下面的示例中,输入行有两个字段,因此根据规范$3应该是“未初始化的值”并且比较等于0。换句话说,$3 == 0应该返回true ,但您可以在下面看到,它返回false:

$ echo '1 2' | awk '{ print($3 == 0 ? "t" : "f") }'
f

“一个真实的AWK”(版本20121220)和GNU AWK(版本4.2.1)的行为相同。这是GNU AWK输出:

$ echo '1 2' | gawk '{ print($3 == 0 ? "t" : "f") }'
f

根据POSIX AWK spec,不存在的字段,例如$3应该是未初始化的值:

  

对不存在的字段(即$ NF之后的字段)的引用应评估为未初始化的值。

另外,如果一个操作数是数字,而另一个是未初始化的值,则应以数字方式进行类似==的比较:

  如果两个操作数都是数字,则

比较(使用'<',“ <=”,“!=”,“ ==”,'>“和”> =“)进行数字比较。数字,另一个具有字符串值,该字符串值是数字字符串,或者一个是数字且另一个具有未初始化的值。否则,应根据需要将操作数转换为字符串...

最后,未初始化值的“数值”应为零:

  

未初始化的值应同时具有数字值为零和空字符串的字符串值。

将此与未初始化的变量进行对比,该变量比较等于零:

$ awk 'BEGIN { print(x == 0 ? "t" : "f") }'
t

因此,在我们的第一个示例中,$3应该是未初始化的值,==应该在数值上进行比较,并且其数值应该为零。因此,在我看来$3 == 0 ? "t" : "f"应该输出t而不是f

任何人都可以帮助我理解为什么不这样做,或者可以帮助我看看我对规范的误读吗?

3 个答案:

答案 0 :(得分:6)

Alfred V. Aho,Brian W. Kernighan和Peter J. Weinberger在 AWK编程语言 中有一段有趣的段落( 1988)(book here):

  

使用数值0和字符串创建未初始化的变量   值""不存在的字段和明确为null的字段仅具有字符串值"";它们不是数字,但是当被强制为数字时,它们将获得数字值0。

     

来源:The AWK Programming Language, section 2.2, p 45

此外:

  

未初始化的变量具有数字值0和字符串值""。因此,   如果x未初始化,

if (x) ...
     

为假,并且

if (!x) ...
if (x == 0) ...
if (x == "") ...
     

都是真的。但是请注意

if (x == "0") ...
     

是错误的。

     

在可能的情况下,字段的类型取决于上下文;例如$1++   表示$1必须强制为数字,并且   $1 = $1 "," $2   表示$1$2将在必要时强制转换为字符串。

     

在无法可靠确定类型的情况下,例如,

if {$1 == $2) ...
     

每个字段的类型取决于输入。 所有字段均为字符串;另外,每个仅包含数字的字段也被视为数字。明确为空的字段具有字符串值"";它们是非数字不存在   字段(即NF之后的字段)和空白行的$0也是如此。

     

与字段一样,由split创建的数组元素也是如此。

     

来源:The AWK Programming Language, Appendix A, Initialization, comparison, and type coercion, p 192

在我看来,这些行很好地解释了观察到的行为,并且似乎大多数程序也遵循此行为。


最重要的是,在rici的帖子的附录中:

调查GNU Awk 4.2.1的源代码时,我发现:

  • 未初始化的变量被分配了名为Node的{​​{1}},它具有以下标志:

    Nnull_string
  • 为不存在的字段分配了名为main.c: Nnull_string->flags = (MALLOC|STRCUR|STRING|NUMCUR|NUMBER); 的节点,该节点重新定义为Null_field为:

    Nnull_string

其中的字段具有值(来自field.c: *Null_field = *Nnull_string; field.c: Null_field->valref = 1; field.c: Null_field->flags = (STRCUR|STRING|NULL_FIELD); /* do not set MALLOC */ ):

awk.h

# define STRING 0x0002 /* assigned as string */ # define STRCUR 0x0004 /* string value is current */ # define NUMCUR 0x0008 /* numeric value is current */ # define NUMBER 0x0010 /* assigned as number */ # define NULL_FIELD 0x2000 /* this is the null field */ 中定义的比较函数int cmp_nodes(NODE *t1, NODE *t2, bool use_strcmp)仅检查eval.cNUMBER中是否都设置了t1标志:

t2

由于if ((t1->flags & NUMBER) != 0 && (t2->flags & NUMBER) != 0) return cmp_numbers(t1, t2); 没有数字字段,因此仅假设它表示一个字符串。这一切似乎都与书中所引用的相符!

此外,来自Null_field

awk.h

答案 1 :(得分:4)

据我所知,您正在正确阅读Posix规范。 Posix规范基于 AWK编程语言(作为informative reference包括在内),但旨在使该语言的某些方面更加精确。特别是,以前处理字符串值和数字值的做法会导致一些奇怪的结果,其中一些在Posix实用程序说明的Rationale section中有所说明。 Posix作者的观点是“历史实现的行为被认为过于直观和不可预测”,并且看一个例子,很难不同意:

$ seq 1 4 | nawk '{
>     a = "+2"
>     b = 2
>     if (NR % 2)
>         c = a + b
>     if (a == b)
>         print "numeric comparison"
>     else
>         print "string comparison"
> }
> '
numeric comparison
string comparison
numeric comparison
string comparison

对空字段和未指定字段值的精确处理是Posix规范与 Awk编程语言定义的awk语言之间的区别之一。因此,最后,您必须决定要考虑哪个规范。

您注意到,Posix清楚地说:(Variables and special values

  

对不存在的字段(即$ NF之后的字段)的引用应评估为未初始化的值。……

实际上,接受此处理的不仅是无效字段。尽管空字符串不是Posix [注1]所定义的“数字字符串”,但空字段还是有例外(如果您明确设置了字段分隔符,则可能是这样):

  

每个字段变量在创建时应具有字符串值或未初始化的值。使用{strong> FS 从$0创建的字段变量应具有未初始化的值,并且该变量不包含任何字符。

如果一个参数是数字而另一个参数是数字,“数字字符串”或未初始化的值,则比较运算符为数字:(Expressions in awk,强调):

  

如果(如果使用'<'"<=""!=""=="'>'">="运算符进行数字比较,则应进行数字比较如果一个是数字并且另一个具有作为数字字符串的字符串值,或者一个是数字并且另一个具有未初始化的值,则两个操作数都是数字。否则,应根据需要将操作数转换为字符串,并进行字符串比较……

但是,这不是Gnu awk的实现,而且显然不是许多其他awks的实现。常见的实现方式:

  • 将空字段和无效字段视为空字符串(不是数字字符串)而不是单位化的值;和

  • 使用数字比较而不是字符串比较来比较两个“数字字符串”。

我找不到及时返回的awk邮件列表的存档,并且萨凡纳的源历史只能追溯到2006年左右,但是Changelog包括以下1997年的条目:

  

Sun Jan 19 23:37:03 1997 Arnold D. Robbins

* field.c (get_field): Add new var that is like Nnull_string but
  does not have numeric attributes, so that new fields are strings.

并且代码仍然反映了该决定。 ({Nnull_string是gawk的未初始化值。现在引用的变量是全局Null_field。)

有趣的是,在BEGIN规则中,gawk(正确地)将$0视为未初始化,而不是空的:

$ gawk 'BEGIN{print $0 == 0, $1 == 0}'
1 0

注释

  1. “数字字符串”是源自用户输入的字符串,其形式为数字。这不包括awk程序中带引号的文字; "1"是一个字符串,而不是数字字符串。上面提到的“ awk表达式”部分列出了数字字符串的可能原点;它们包括字段,环境变量和命令行选项,并且该属性通过分配保留。

    该部分还定义了具有数字形式的数字,其中为实现提供了两种选择:

    • 使用strtod的等效项,但附加的约束是,解析的数字必须至少包含一个字符,并且所有尾随字符都应为空格;

    • 使用awk语法中NUMBER的词汇定义。

    这两种可能性均不允许空字符串成为数字字符串。

答案 2 :(得分:2)

在讨论此问题时,POSIX标准似乎比必要时更令人困惑,但请查看the "Expressions in awk" part of the POSIX standard中表中的以下语句:

Syntax |      Name       | Type of Result | Associativity
$expr  | Field reference |    String      |     N/A

因此,$<whatever>的类型默认为字符串。现在,让我们看看该部分对它如何成为数字字符串的说明:

A string value shall be considered a numeric string if it comes from one of the following:

    Field variables

    <other N/A stuff - Ed.>

and an implementation-dependent condition corresponding to either case (a) or (b) below is met.

    a) After the equivalent of the following calls to functions defined by the ISO C standard, string_value_end would differ from string_value, and any characters before the terminating null character in string_value_end would be <blank> characters:

    char *string_value_end;
    setlocale(LC_NUMERIC, "");
    numeric_value = strtod (string_value, &string_value_end);

传递NULL字符串后,strtod()将返回0,但string_value_end与string_value相同,因此上述测试不会将NULL识别为数字字符串。

    b) After all the following conversions have been applied, the resulting string would lexically be recognized as a NUMBER token as described by the lexical conventions in Grammar :

        All leading and trailing <blank> characters are discarded.

        If the first non- <blank> is '+' or '-', it is discarded.

        Each occurrence of the decimal point character from the current locale is changed to a <period>.
根据以上分析,

NULL也不会被识别为NUMBER令牌。

因此,根据上述情况,仅当输入值“看起来像”一个数字(当然不是NULL时才是数字)时,才将输入字段视为数字字符串,因此未填充的$<whatever>只是具有值的字符串NULL,任何涉及字符串的比较都是字符串比较(有关恕我直言,请参见https://www.gnu.org/software/gawk/manual/gawk.html#Variable-Typing的表,以最清楚地表示比较类型),因此它永远不会等于任何数字,包括0,因为{{1} }实际上被视为$X == 0,与$X == "0"为NULL时的"" == "0"相同。