在awk字段分隔符更改后重新评估记录中的字段

时间:2014-07-16 09:51:58

标签: macos awk

(这是我在这里发表的第一篇文章,如果我以错误的方式提问,请原谅我。)

我在OSX Maverick上学习awk。我在awk上经历了this tutorial

我正在尝试重现类似于该教程中awk_example4a.awk的内容。

所以我想出了这个awk程序/脚本/参数(不确定你叫什么?):

BEGIN { i=1 }
{
    print "Line " i;
    print "$1 is " $1,"\n$2 is " $2, "\n$3 is " $3;
    FS=":";
    $0=$0;
    print "With the new FS - line " i;
    print "$1 is " $1,"\n$2 is " $2, "\n$3 is " $3;
    FS=" ";
    i++;
}

输入文件如下所示:

A1 B1:B2 C2
A1:A2 B2:B3 C3

我要做的是首先使用默认的FS(空格)处理每一行/记录,然后使用新的FS(“:”)重新处理它们,然后在转到下一条记录之前恢复默认FS

根据教程,$0=$0应该让awk使用新的字段分隔符重新评估字段,因此可以给我一个如下所示的输出:

Line 1
$1 is A1 
$2 is B1:B2 
$3 is C2
With the new FS - line 1
$1 is A1 B1
$2 is B2 C2
$3 is
Line 2
$1 is A1:A2 
$2 is B2:B3 
$3 is C3
With the new FS - line 2
$1 is A1
$2 is A2 B2
$3 is B3 C3

但相反,我得到了:

Line 1
$1 is A1 
$2 is B1:B2 
$3 is C2
With the new FS - the line 1
$1 is A1 
$2 is B1:B2 
$3 is C2
Line 2
$1 is A1:A2 
$2 is B2:B3 
$3 is C3
With the new FS - the line 2
$1 is A1:A2 
$2 is B2:B3 
$3 is C3

即。 FS更改后,字段尚未重新评估。

因此,如果$0=$0不起作用(也不会执行$1=$1; $2=$2之类的操作),如何使用不同的FS让awk重新评估同一行?

谢谢。

2 个答案:

答案 0 :(得分:1)

TL; DR:

FreeBSD / OS X awk不会将更改应用于FS(字段分隔符),直到当前记录完成处理后 - 此行为实际上是 POSIX-mandated (见下文)。

解决方法请勿更改FS并改为使用功能split()

{
    print "Line " ++i
    print "$1 is " $1 "\n$2 is " $2 "\n$3 is " $3
    split($0, flds, ":")   # split current line by ':' into array `flds`
    print "With the new FS - line " i
    print "field1 is " flds[1] "\nfield2 is " flds[2] "\nfield3 is " flds[3]
}
  • 请注意如何在数字上下文中依赖未初始化的变量默认为BEGIN来消除0块。
  • ,语句中删除了print个实例,因为每个实例都会插入一个空格(输出字段分隔符的默认值OFS),这是不需要的这种情况。
  • 鉴于语句是换行符分隔的,不需要;来终止它们。

继续阅读,了解有趣的多平台兼容性详情。


POSIX spec. for awk州(强调我的):

Before the first reference to a field in the record is evaluated, the record shall be 
split into fields, according to the rules in Regular Expressions, 
**using the value of FS that was current at the time the record was read**.

关于为$0或特定字段分配新值,相同的来源说明:

The symbol $0 shall refer to the entire record; setting any other field causes 
the re-evaluation of $0. Assigning to $0 shall reset the values of all other
fields and the NF built-in variable.

换句话说:假设重新赋值情况没有另外说明,则只能引用POSIX规范中给定FS值的范围。要求它对于给定的输入记录是常量肯定有歧义,如果规范肯定会有所帮助。解决了 - 那就是说,保守而且更安全的解释是假设常数 - 处理 - 给定 - 记录 FS

因此,FreeBSD / OS X awk是模范公民,而 GNU awkmawk提供更多的灵活性,不遵守规则和在重新分配到FS或任何特定字段时,将$0更改为当前记录。

但请注意,GNU awk(自v4.1.1起)甚至不会使用--posix选项更改该行为,其明确意图是导致符合POSIX的行为。 如果我正在阅读POSIX规范。正确(告诉我我是否),这应该被视为 bug

答案 1 :(得分:0)

这太傻了。我可能在我的Mac上有相同版本的awk,并且能够在Snow Leopard上重现这一点。

% awk -version
awk version 20070501
使用FS技术在此版本的awk中处理同一行时,

$0=$0似乎无法重新分配。可以通过引入$0重新分配getline。它应该被认为是矫枉过正 - 特别是因为它在子流程中发出命令 - 但没有其它方法对我有效。

这是一个可执行的awk脚本:

#!/usr/bin/awk -f

BEGIN { i=1 }

{
    print "Line " i
    print "$1 is " $1 "\n$2 is " $2 "\n$3 is " $3
    FS=":"

    # here's the trick
    cmd = sprintf( "echo %s", $0 )
    cmd | getline
    close(cmd)

    print "With the new FS - line " i
    print "$1 is " $1 "\n$2 is " $2 "\n$3 is " $3
    FS=" " # this will work for the next line
    i++
}

cmd是使用sprintf()构建的,因为以这种方式正确构建cmd更容易。然后运行cmd,输出通过管道传递给getline,将$0重新分配给命令的输出(当前行的echo),重新计算字段分隔符。之后,关闭cmd以防止管道泄漏。

正如其他人所说,这可以在GNU awk中使用,它很容易在Mavericks中安装,但在Snow Leopard上很痛苦。