是否有一个字段可以存储在正则表达式中使用的确切字段分隔符 FS,相当于 RS 的 RT?

fed*_*qui 11 awk gnu

GNU Awk 的 4.1.2 Record Splitting with 中,gawk我们可以阅读:

RS是单个字符时,RT包含相同的单个字符。但是,whenRS是正则表达式,RT包含与正则表达式匹配的实际输入文本。

这个变量RT某些情况下非常有用。

同样,我们可以设置一个正则表达式作为字段分隔符。例如,在这里我们允许它是“;” 或“|”:

$ gawk -F';' '{print NF}' <<< "hello;how|are you"
2  # there are 2 fields, since ";" appears once
$ gawk -F'[;|]' '{print NF}' <<< "hello;how|are you"
3  # there are 3 fields, since ";" appears once and "|" also once
Run Code Online (Sandbox Code Playgroud)

但是,如果我们想再次打包数据,我们没有办法知道两个字段之间出现了哪个分隔符。因此,如果在前面的示例中我想遍历字段并使用 再次将它们打印在一起FS,它会在每种情况下打印整个表达式:

$ gawk -F'[;|]' '{for (i=1;i<=NF;i++) printf ("%s%s", $i, FS)}' <<< "hello;how|are you"
hello[;|]how[;|]are you[;|]  # a literal "[;|]" shows in the place of FS
Run Code Online (Sandbox Code Playgroud)

有没有办法使用用于拆分每个字段的特定字段分隔符来“重新打包”字段,类似于 RT 允许做的事情?

(问题中给出的例子相当简单,但只是为了说明这一点)

anu*_*ava 8

有没有办法使用用于拆分每个字段的特定字段分隔符来“重新打包”字段

使用gnu-awk split()它使用提供的正则表达式为匹配的分隔符提供额外的第四个参数:

s="hello;how|are you"
awk 'split($0, flds, /[;|]/, seps) {for (i=1; i in seps; i++) printf "%s%s", flds[i], seps[i]; print flds[i]}' <<< "$s"

hello;how|are you
Run Code Online (Sandbox Code Playgroud)

一个更易读的版本:

s="hello;how|are you"
awk 'split($0, flds, /[;|]/, seps) {
   for (i=1; i in seps; i++)
      printf "%s%s", flds[i], seps[i]
   print flds[i]
}' <<< "$s"
Run Code Online (Sandbox Code Playgroud)

注意第四个seps参数,split它通过第三个参数 ie 中使用的正则表达式存储匹配文本的数组/[;|]/

当然,它不像RS,ORS和那样简短RT,可以写成:

awk -v RS='[;|]' '{ORS = RT} 1' <<< "$s"
Run Code Online (Sandbox Code Playgroud)

  • 这里可能值得一提的是,您不能将常量正则表达式传递给用户定义的函数,因此虽然您可以执行`split($0,arr,/re/)`,但您不能编写自己的函数`foo() ` 并执行 `foo($0,arr,/re/)`,您必须将其称为 `foo($0,arr,"re")`,而不是使用动态正则表达式,因为在该上下文中为 `/re/`表示“($0 ~ /re/ ? 1 : 0)”。GNU awk 有一个称为强类型正则表达式的增强功能,它通过在常量正则表达式前加上“@”前缀来解决该问题,例如“foo($0,arr,@/re/)” - 请参阅 https://www.gnu.org/软件/gawk/manual/gawk.html#Strong-Regexp-Constants (3认同)
  • 您可能应该更改(或讨论)的另一件事是循环范围 - 使用默认 FS 时,“seps”数组可以从 0 开始,因为 seps[0] 然后保存 flds[1] 之前出现的空白,并且通常是在字段分割期间被丢弃。 (2认同)
  • 哇,很高兴知道。我从来不知道“@/re/” (2认同)
  • @fedorqui'SOstopharming' 它们可以作为 `split()` 的参数,你只是忘记引用用于初始化 awk 变量的字符串,尝试 `gawk -v patt='@/;/' '{print split ($0, a, patt)}' &lt;&lt;&lt; "ha;he;hi"` 所以中间的 `;` 终止了命令行。反而。**始终**在 shell 中引用字符串和脚本,除非您有特定原因 **需要** 不这样做。 (2认同)
  • 哎呀,你是对的,@EdMorton!事实上,现在我注意到我以另一种方式理解文档:它在 split 和其他中,可以使用它(_强类型正则表达式常量不能在常规正则表达式常量可以使用的任何地方使用,因为这将使语言更加令人困惑。相反,您只能在某些上下文中使用它们_) (2认同)

Ed *_*ton 5

作为@anubhava提到,GAWK具有split()(并且patsplit()这是FPAT因为split()FS-看https://www.gnu.org/software/gawk/manual/gawk.html#String-Functions)做你想要什么。如果您想要与 POSIX awk 相同的功能,那么:

$ cat tst.awk
function getFldsSeps(str,flds,fs,seps,  nf) {
    delete flds
    delete seps
    str = $0

    if ( fs == " " ) {
        fs = "[[:space:]]+"
        if ( match(str,"^"fs) ) {
            seps[0] = substr(str,RSTART,RLENGTH)
            str = substr(str,RSTART+RLENGTH)
        }
    }

    while ( match(str,fs) ) {
        flds[++nf] = substr(str,1,RSTART-1)
        seps[nf]   = substr(str,RSTART,RLENGTH)
        str = substr(str,RSTART+RLENGTH)
    }

    if ( str != "" ) {
        flds[++nf] = str
    }

    return nf
}

{
    print
    nf = getFldsSeps($0,flds,FS,seps)
    for (i=0; i<=nf; i++) {
        printf "{%d:[%s]<%s>}%s", i, flds[i], seps[i], (i<nf ? "" : ORS)
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意上面对字段分隔符的具体处理," "因为这意味着与所有其他字段分隔符值不同的两件事:

  1. 字段实际上由任何空白的链分隔,并且
  2. 在填充 $1(或本例中的 flds[1])时将忽略前导空白,因此为了我们的目的,必须在 sep[0]` 中捕获空白,因为每个 sep[N] 都是关联的与之前的 flds[N] 。

例如,在这 3 个输入文件上运行上面的代码:

$ head file{1..3}
==> file1 <==
hello;how|are you

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you
Run Code Online (Sandbox Code Playgroud)

我们会得到以下输出,其中每个字段显示为字段编号,然后是字段值,[...]然后是分隔符<...>,所有内容都在{...}(请注意,seps[0]填充了 FS 所在的 IFF" "并且记录以空格开头):

$ awk -F'[,|]' -f tst.awk file1
hello;how|are you
{0:[]<>}{1:[hello;how]<|>}{2:[are you]<>}

$ awk -f tst.awk file2
hello how are_you
{0:[]<>}{1:[hello]< >}{2:[how]< >}{3:[are_you]<>}

$ awk -f tst.awk file3
    hello how are_you
{0:[]<    >}{1:[hello]< >}{2:[how]< >}{3:[are_you]<>}
Run Code Online (Sandbox Code Playgroud)