gawk FS将记录分成单个字符

时间:2014-02-26 14:24:17

标签: awk gawk

如果字段分隔符为空字符串,则每个字符将成为单独的字段

$ echo hello | awk -F '' -v OFS=, '{$1 = NF OFS $1} 1'
5,h,e,l,l,o

但是,如果FS是一个可能匹配零次的正则表达式,那么会发生相同的行为:

$ echo hello | awk -F ' *' -v OFS=, '{$1 = NF OFS $1} 1'
1,hello

任何人都知道为什么会这样吗?我在gawk manual找不到任何内容。 FS=""只是一个特例吗?

我最感兴趣的是理解为什么第二种情况不会将记录分成更多字段。好像awk正在处理FS=" *" FS=" +"

5 个答案:

答案 0 :(得分:4)

有趣的问题!

我刚刚提取了gnu-awk 4.1.0的代码,我想我们可以在field.c文件中找到答案。

line 371:
 * re_parse_field --- parse fields using a regexp.
 *
 * This is called both from get_field() and from do_split()
 * via (*parse_field)().  This variation is for when FS is a regular
 * expression -- either user-defined or because RS=="" and FS==" "
 */
static long
re_parse_field(lo...

也是这一行:(line 425):

if (REEND(rp, scan) == RESTART(rp, scan)) {   /* null match */

这是您问题中<space>*匹配的情况。实现没有增加nf,也就是说,它认为整行是一个单独的字段。请注意,此功能也用于do_split()功能。

首先,如果FS为空字符串,则gawk将每个字符分隔为其自己的字段。 gawk的文档清楚地写了这个,也在代码中,我们可以看到:

line 613:
 * null_parse_field --- each character is a separate field
 *
 * This is called both from get_field() and from do_split()
 * via (*parse_field)().  This variation is for when FS is the null string.
 */
static long
null_parse_field(long up_to,

如果FS有单个字符,awk不会将其视为正则表达式。这也在doc中提到过。也在代码中:

#line 667
 * sc_parse_field --- single character field separator
 *
 * This is called both from get_field() and from do_split()
 * via (*parse_field)().  This variation is for when FS is a single character
 * other than space.
 */
static long
sc_parse_field(l

如果我们读取该函数,那里没有进行正则表达式匹配处理。

在函数re_parse_field()sc_parse_field()的评论中,我们看到do_split也会调用它们。它解释了为什么我们在以下命令中使用1而不是3

kent$  echo "foo"|awk '{split($0,a,/ */);print length(a)}'
1

注意,为避免帖子过长,我没有在这里粘贴完整的代码,我们可以在这里找到代码:

http://git.savannah.gnu.org/cgit/gawk.git/

答案 1 :(得分:2)

如前所述,空字段分隔符会生成未定义的行为;相同的代码将在awk的不同平台/风格上给出不同的结果。例如(所有Mac OSX 10.8.5):

> echo hello | awk -F '' -v OFS=, '{$1 = NF OFS $1} 1'
awk: field separator FS is empty

1,hello

所以awk抱怨,但继续前进。

让我们看看其他一些例子:

> echo hello | awk -F '.' -v OFS=, '{$1 = NF OFS $1} 1'
1,hello

.本身不被视为正则表达式

> echo hello | awk -F '[.]' -v OFS=, '{$1 = NF OFS $1} 1'
1,hello

仍然没有

> echo hello | awk -F '.?' -v OFS=, '{$1 = NF OFS $1} 1'
6,,,,,,

现在我们有类似正则表达式的东西:.?是“零或一个字符”。它被扩展为一个字符(被消耗),因此输出是“很多无用的”

> echo hello | awk -F '*' -v OFS=, '{$1 = NF OFS $1} 1'
1,hello

不是正则表达式

> echo hello | awk -F '.*' -v OFS=, '{$1 = NF OFS $1} 1'
2,,

使用整个事物的正则表达式

> echo hello | awk -F 'l' -v OFS=, '{$1 = NF OFS $1} 1'
3,he,,o

匹配字母l两次 - 两个空字符串

> echo hello | awk -F 'ell' -v OFS=, '{$1 = NF OFS $1} 1'
2,h,o

一次匹配所有ell

> echo hello | awk -F '.?|' -v OFS=, '{$1 = NF OFS $1} 1'
awk: illegal primary in regular expression .?| at 
 input record number 1, file 
 source line number 1

尝试聪明:有时一边有空字符串的|会匹配“任何”,但awk的正则表达式引擎不喜欢它。

结论 - 正则表达式不能匹配“空”,并且消耗了匹配的任何内容。尝试使用(?:.)甚至(?=.)会产生错误。

答案 2 :(得分:1)

似乎是special case in gawk

  

传统上,FS的行为等于“”未定义。在这   大多数版本的Unix awk只是将整个记录视为唯一   有一个领域。 (d.c.)在兼容模式下(参见选项),如果是FS   空字符串,然后gawk也表现这种方式。

答案 3 :(得分:1)

POSIX对此有何评论:

  

如果FS是空字符串,则行为未指定。

因此gawk行为是特定于实现的,并解释了为什么您的两个示例不会产生相同的输出。

答案 4 :(得分:0)

另一个数据点:gawk和perl不同意如何做到这一点:

$ perl -E '$,=","; $s="hello"; $r=qr( *); @s=split($r,$s); say scalar(@s), @s'
5,h,e,l,l,o

$ gawk 'BEGIN {s="hello";r=" *";n=split(s,a,r); print n,a[n]; if (s~r) print "match"}'
1 hello
match
$ gawk 'BEGIN {s="hello";r="";  n=split(s,a,r); print n,a[n]; if (s~r) print "match"}'
5 o
match