【问题标题】:Re-evaluating fields in a record after awk field separator changeawk 字段分隔符更改后重新评估记录中的字段
【发布时间】:2014-07-17 14:27:23
【问题描述】:

(这是我在这里的第一篇文章,所以如果我问错了问题,请原谅我。)

我正在我的 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 之类的东西也不起作用),我如何让 awk 使用不同的 FS 重新评估同一行?

谢谢。

【问题讨论】:

  • 如果我打印 print "With the new FS - line " i; print "$1 is", $1 和到目前为止,对我工作。调查原因。
  • 使用与您发布的完全相同的脚本为我工作?
  • 如何启动它?你文件中的shebang是什么? #!/bin/bash#!/usr/bin/awk -f 或其他?
  • 我的确切命令行是:cat input.txt | awk -f test.awk。并且test.awk 在文件的开头有一个额外的#!/bin/awk -f。编辑:我将shebang更改为#!/usr/bin/awk -f,结果没有改变。
  • 一种解决方法是将 $0 的每个元素分配给一个数组并从那里处理和打印。我最近也遇到了尝试像这样使用FS 的问题。就 Mac OSX 而言,我建议安装 Homebrew,这样您就可以无缝安装 GAWK(即 GNU AWK),这种东西可能更灵活,而且 afaik 无论如何都是大多数 Linux 系统上的标准 AWK 实现. Mavericks 附带的 AWK(我认为)是 BSD 的 AWK 的一些变体,无论如何它缺少 GAWK 的一些不错的功能。

标签: macos awk


【解决方案1】:

tl;博士:

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

解决方法不要更改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 状态(强调我的):

在评估对记录中某个字段的第一次引用之前,该记录应 拆分成字段,根据正则表达式中的规则, **使用读取记录时当前的 FS 值**。

关于为$0 或特定字段分配新值,同一来源指出:

符号 $0 指整个记录;设置任何其他字段原因 重新评估$ 0。分配给 $0 将重置所有其他的值 字段和 NF 内置变量。

换句话说:鉴于重新分配的情况没有另外说明,POSIX 规范中对给定FS 值范围的唯一引用。要求它对于给定的输入记录是恒定的肯定有歧义,如果规范肯定会有所帮助。解决了这个问题 - 也就是说,保守且因此更安全的解释是假设 constant-while-processing-a-given-record FS.

因此,FreeBSD/OS X awk 是模范公民,而 GNU awkmawk 通过不遵守规则并应用 FS 更改甚至提供更大的灵活性到当前重新分配给$0或任何特定字段的记录

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

【讨论】:

  • 首先感谢您的解决方案。该代码对我来说工作得很好。其次,感谢您整理代码(例如,我不知道是否需要尾随 ;。)第三,感谢您提供有关 POSIX 合规性的背景信息。最后,那个“tl;dr”是一个很好的接触。谢谢你的帮助!!
  • @LD99:我很高兴听到这个消息;我的荣幸。跨平台兼容性是一件棘手的事情——您会发现这里的许多人在回答 awk 问题时假设 GNU awk,因此了解它们之间的差异会很有帮助。大多数情况下(这种情况是一个例外),FreeBSD/OSX awk 的功能是 GNU awk 的 子集,所以如果您将自己限制在 FreeBSD/OSX awk 的功能上,它很有可能同时适用于两者- 并且,鉴于 FreeBSD/OSX awk 保持接近 POSIX 标准,在其他 awk 实现中也是如此。但是,GNU awk 添加了许多非常有用的特性。
【解决方案2】:

这很愚蠢。我的 Mac 上可能有相同版本的 awk,并且能够在 Snow Leopard 上重现。

% awk -version
awk version 20070501

FS 在此版本的 awk 中使用 $0=$0 技术处理同一行时,似乎无法重新分配。通过引入getline,可以重新分配$0。它应该被认为是矫枉过正 - 特别是因为它在子进程中发出命令 - 但没有其他任何东西对我有用。

这是一个可执行的 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 上很痛苦。

【讨论】:

  • 感谢您的解决方案和解释。我对其进行了测试,它运行良好——尽管我对cmd 命令一无所知,稍后将不得不对其进行更深入的研究。顺便说一句,我使用的是 OSX Mavericks,并且我有相同版本的 awk (20070501)。再次感谢!!
  • 不要这样做!这是危险,因为通过 echo 命令的任何数据都可能包含 shell 命令,因此 awk 的输入可以由 shell 执行。使用$(uname -a) 作为输入,你会看到。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-02
  • 2017-11-14
  • 2014-10-14
  • 2016-11-06
  • 1970-01-01
相关资源
最近更新 更多