awk的默认字段分隔符是什么?

29

awk的默认分隔符只有空格吗?


3
默认的字段分隔符或字段分隔符(FS)是[ \t]+,即一个或多个空格和制表符。 - Thor
2
@Thor 不是这样的。请查看手册页。 - Ed Morton
1
@EdMorton:没错,我忘记了换行符,即FS='[ \t\n]+'。但是只有在RS不包括换行符时才会产生影响。 - Thor
1
@Thor 不完全准确,因为即使您有包含换行符的 RS,如果您构造一个包含换行符的字符串并执行 split(string,arr),默认的 FS 也会产生影响。 - Ed Morton
1
好问题,一点也不愚蠢。 - Timo
4个回答

30
以下是适用于所有主要Awk实现的实用摘要:
  • GNU Awk(gawk) - 一些Linux发行版中的默认awk
  • Mawkmawk) - 一些Linux发行版中的默认awk(例如,早期版本的Ubuntu crysman报告称19.04版本现在附带GNU Awk-请参见他下面的评论。
  • BWK Awk - 包括macOS在内的类似BSD的平台上的默认awk
在Linux上,awk -W version将告诉您默认awk的哪个实现。
BWK Awk 理解awk --version(GNU Awk除了awk -W version外还理解
最近版本的所有这些实现都遵循 POSIX标准关于字段分隔符的规定[1](但不包括记录分隔符)。
术语表:
  • RS输入记录分隔符,它描述了输入如何被分成记录

    • POSIX规定的默认值是一个换行符,也称为下面的\n;也就是说,默认情况下输入被分成行
    • awk的命令行上,可以将RS指定为-v RS=<sep>
    • POSIX将RS限制为字面上的单个字符,但GNU Awk和Mawk支持多字符值,这些值可能是扩展正则表达式(BWK Awk不支持)。
  • FS输入字段分隔符,它描述了每个记录如何被分割成字段;它可以是一个扩展正则表达式

    • awk的命令行上,可以将FS指定为-F <sep>(或-v FS=<sep>)。
    • POSIX规定的默认值形式上是一个空格0x20),但该空格不是字面上解释为(唯一的)分隔符,而具有特殊含义;请参见下文。

默认情况下

  • 任何空格或者 制表符和/或换行符都被视为字段分隔符
  • 前导和尾随的空格被忽略。

POSIX规范使用抽象的<blank>表示空格和制表符, 这对于所有语言环境都是正确的,但在特定的语言环境下,可能会包括其他字符 - 我不知道是否存在这样的语言环境。

请注意,使用默认的输入记录分隔符 (RS),\n换行符通常不会成为字段分隔符,因为在这种情况下,没有记录本身包含\n

然而,作为字段分隔符的换行符确实会发挥作用:

  • RS设置为导致记录本身包含\n实例的值时(例如当RS设置为空字符串时;见下文)。
  • 通常情况下,当使用split()函数将字符串拆分为数组元素时没有明确的字段分隔符参数。
    • 即使在默认RS生效的情况下,输入记录不会包含\n实例,但是如果在来自不同来源的多行字符串上调用split()函数(例如通过-v选项传递的变量或伪文件名),split()函数总是\n视为字段分隔符。

重要的非默认考虑因素:

  • 将空字符串赋值给RS有特殊含义:它以段落模式读取输入,这意味着输入被非空行的连续运行分成记录,忽略前导和尾随的空行。

  • 当你将除了字面空格之外的任何东西赋值给FS时,FS的解释会发生根本性的变化

    • 单个字符或指定字符集中的每个字符都会被单独识别为字段分隔符 - 而不是像默认情况下那样识别它们的连续运行
      • 例如,将FS设置为[ ] - 即使它实际上相当于一个空格 - 也会导致每个记录中的每个单独空格实例被视为字段分隔符。
      • 要识别连续运行,必须使用正则表达式量词符(重复符号)+;例如,[\t]+将识别制表符的连续运行作为单个分隔符。
    • 前导和尾随分隔符不会被忽略,而是将它们视为空字段的分隔符
    • FS设置为空字符串意味着每个记录的每个字符都是自己的字段
  • 根据 POSIX标准, 如果RS设置为空字符串(段落模式),则换行符\n)也会被视为字段分隔符,无论FS的值如何。


[1] 不幸的是,GNU Awk至少在版本4.1.3中遵守了一个已过时的POSIX标准,当你使用强制POSIX兼容选项-P (--posix)时,关于字段分隔符:当该选项生效并且RS设置为非空值时,换行符(\n实例)不被认为是字段分隔符。GNU Awk手册详细说明了过时的行为(但忽略了当RS设置为空字符串时不适用的事实)。POSIX标准在2008年进行了更改(请参见评论),以将新行视为字段分隔符,当FS具有其默认值时 - 正如GNU Awk一直没有使用-P (--posix)所做的那样。
以下是2个验证上述行为的命令:

  • 如果启用-P并将RS设置为空字符串,则\n仍然被视为字段分隔符:
    gawk -P -F' ' -v RS='' '{ printf "<%s>, <%s>\n", $1, $2 }' <<< $'a\nb'
  • 如果启用-P并且RS不为空,则\n不会被视为字段分隔符-这是过时的行为:
    gawk -P -F' ' -v RS='|' '{ printf "<%s>, <%s>\n", $1, $2 }' <<< $'a\nb'
    根据GNU Awk维护者的说法,正在进行修复,预计在版本4.2中实现(无时间表)。
    (对@JohnKugelman和@EdMorton的帮助表示感谢。)

谢谢mklement0,我也看了John的回复,似乎只有空格是默认分隔符?但是你提到了空格和制表符?如果我错了,请随时纠正我。 :) - Lin Ma
1
简而言之:虽然空格在正式上是默认的FS值,但它代表着空格、制表符和换行符。我已经更新了我的答案以作澄清。 - mklement0
1
POSIX标准已更改,gawk支持旧版本。请参阅2004年标准(http://pubs.opengroup.org/onlinepubs/009695399/utilities/awk.html),其中指出“字段是非<blank>字符串”与2013年标准(http://pubs.opengroup.org/onlinepubs/9699919799/utilities/awk.html)相比,该标准指出“字段是非<blank>非<newline>字符的字符串”。您应该向bug-gawk@gnu.org发送电子邮件。 - Ed Morton
1
@EdMorton:事实证明我误读了POSIX规范,你是正确的:在POSIX.1-2008(SUS v4)中确实发生了你所描述的变化。根据你的建议(感谢),我已经向bug-gawk@gnu.org发送了电子邮件,并且已经收到回复说修复将在“下一个主要版本”中推出,但我不清楚那是哪个版本。 - mklement0
1
@mklement0 看起来Ubuntu 19.04同时带有gawk和mawk,你可以在这里找到它们:http://releases.ubuntu.com/19.04/ubuntu-19.04-desktop-amd64.manifest 。但是,gawk是默认的:❱ which -a "awk" /usr/bin/awk ❱ file /usr/bin/awk /usr/bin/awk: symbolic link to /etc/alternatives/awk ❱ file /etc/alternatives/awk /etc/alternatives/awk: symbolic link to /usr/bin/gawk - crysman
谢谢,@crysman - 我已经更新了答案,指向了你的评论。 - mklement0

12

问题 默认的分隔符是否只是空格用于awk? 模糊不清,但我会尝试回答您可能会问的两个问题。

FS 变量的默认值(它保存了告诉awk如何将记录分隔成字段的字段分隔符)是一个单独的空格字符。

awk 用于将记录分隔成字段的东西是“字段分隔符”,它是一个正则表达式,具有一些附加功能,仅在字段分隔符为单个空白字符时才适用。这些额外的功能是:

  1. 领先和尾随的空格在字段拆分期间被忽略。
  2. 字段在连续的空格字符链上分隔,其中包括空格、制表符和换行符。
  3. 如果要使用字面空格字符作为字段分隔符,必须将其指定为 [ ] 而不仅仅是独立的字面空格字符,就像在正则表达式中一样。

除了在读取输入时将字段分隔符用于将记录拆分为字段,它们还在某些其他上下文中使用,例如 split() 的第三个参数,因此重要的是您知道哪些上下文需要字符串、正则表达式或字段分隔符,man 页面清楚地指定了每个上下文。

除其他事项外,以上内容还解释了这一点:

$ echo ' a b c ' | awk '{printf "%d: <%s> <%s> <%s>\n", NF, $1, $2, $3}'
3: <a> <b> <c>
$ echo ' a b c ' | awk -F' ' '{printf "%d: <%s> <%s> <%s>\n", NF, $1, $2, $3}'
3: <a> <b> <c>
$ echo ' a b c ' | awk -F'[ ]' '{printf "%d: <%s> <%s> <%s>\n", NF, $1, $2, $3}'                              
5: <> <a> <b>

所以,如果您不明白为什么前两个产生相同的输出但最后一个不同,请询问。


请不要将“blank”与“space”混淆。 “space”是实际的空格字符(0x20),而“blank”是一种可能特定于语言环境的抽象:“在POSIX语言环境中,只包括<space>和<tab>。在语言环境定义文件中,<space>和<tab>会自动包含在这个类别中。”(我在POSIX规范中看不到涵盖“blank”和换行符的总称。) - mklement0
否则,这是一个很棒的答案。 - mklement0

8
让我们来看一下GNU awk的手册页面:
“FS——输入字段分隔符,默认为一个空格。请参见上面的‘字段’部分。”
前往“字段”部分!
“当读取每个输入记录时,gawk使用FS变量的值作为字段分隔符将记录分成字段。如果FS是单个字符,则字段由该字符分隔。如果FS是空字符串,则每个单独的字符都成为单独的字段。否则,FS应该是完整的正则表达式。特殊情况下,如果FS是单个空格,则字段由连续的空格、制表符和/或换行符分隔。

嗨John,你的回复让我有点迷惑。这是指只使用空格作为默认分隔符,还是同时使用空格/制表符作为默认分隔符? - Lin Ma
仅为补充此答案:尽管引用自GNU Awk手册页面,但它们也适用于其他一些Linux发行版默认提供的Awk实现,如Mawk(mawk;例如,在Ubuntu上)- 它们也适用于BWK Awk,如在类似BSD的平台上,包括macOS。 - mklement0
1
@mklement0:diff注释:现在贫民窟的BWK awk还能处理RS中的正则表达式:jot -s''-c-33 126 | gtr -d'\n' | nawk'$-_ = NR"=NR:{ "($-_)" }:NF=" NF' RS='(:|[0-9]|\42)+' 1=NR:{!}:NF=1 2=NR:{#$%&'()*+,-./}:NF=1 3=NR:{;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_\abcdefghijklmnopqrstuvwxyz{|}~}:NF=1` - RARE Kpop Manifesto

2

'[ ]+' 对我有效。 运行 awk -W version 获取 awk 版本。我的版本是 GNU Awk 4.0.2

# cat a.txt
tcp        0      0 10.192.25.199:65002     0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:26895         0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:111             0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:18422           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN
tcp        0      0 10.192.25.199:8888      0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:50010           0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:50075           0.0.0.0:*               LISTEN
tcp        0      0 10.192.25.199:8093      0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:8670            0.0.0.0:*               LISTEN

例如,我想获取监听端口。因此,我需要使用添加有“:”的awk默认分隔符。
# cat a.txt  | awk -F '[ ]+|:' '{print $5}'
65002
26895
111
18422
22
8888
50010
50075
8093
8670

如果你只是想测试默认分隔符,你可以运行以下命令:

# cat a.txt  | awk -F '[ ]+' '{print $4}'
10.192.25.199:65002
127.0.0.1:26895
0.0.0.0:111
0.0.0.0:18422
0.0.0.0:22
10.192.25.199:8888
0.0.0.0:50010
0.0.0.0:50075
10.192.25.199:8093
0.0.0.0:8670

结果和预期一致。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接