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

Is there a field that stores the exact field separator FS used when in a regular expression, equivalent to RT for RS?

GNU Awk's 4.1.2 Record Splitting with gawk中我们可以读到:

When RS is a single character, RT contains the same single character. However, when RS is a regular expression, RT contains the actual input text that matched the regular expression.

这个变量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

但是,如果我们想再次打包数据,我们没有办法知道两个字段之间出现了哪个分隔符。因此,如果在前面的示例中我想遍历字段并使用 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

有没有一种方法可以使用用于拆分每个字段的特定字段分隔符来“重新打包”字段,类似于 RT 允许的操作?

(题中举的例子比较简单,只是为了说明问题)

Is there a way to "repack" the fields using the specific field separator used to split each one of them

使用 gnu-awk split() 使用提供的正则表达式为匹配的定界符提供额外的第 4 个参数:

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

hello;how|are you

更具可读性的版本:

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

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

当然不像RSORSRT那么简单,可以写成:

awk -v RS='[;|]' '{ORS = RT} 1' <<< "$s"

拆分的另一种选择是使用匹配来查找字段分隔符并将它们读入数组:

awk -F'[;|]' '{
    str=[=10=]; # Set str to the line
    while (match(str,FS)) { # Loop through rach match of the field separator
      map[cnt+=1]=substr(str,RSTART,RLENGTH); # Create an array of the field separators
      str=substr(str,RSTART+RLENGTH) # Set str to the rest of the string after the match string
    }
    for (i=1;i<=NF;i++) { 
      printf "%s%s",$i,map[i] # Loop through each record, printing it along with the field separator held in the array map.
    } 
    printf "\n" 
   }' <<< "hello;how|are you"

作为, gawk has split() (and patsplit() which is to FPAT as split() is to FS - see 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 = [=10=]

    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([=10=],flds,FS,seps)
    for (i=0; i<=nf; i++) {
        printf "{%d:[%s]<%s>}%s", i, flds[i], seps[i], (i<nf ? "" : ORS)
    }
}

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

  1. 字段实际上由任何白色的链分隔 space,和
  2. 前导白色 space 在填充 $1(或本例中的 flds[1])时将被忽略,因此如果白色 space 存在,则必须在 seps[0] 中捕获]` 出于我们的目的,因为每个 seps[N] 都与它之前的 flds[N] 相关联。

例如,运行 上述 3 个输入文件:

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

==> file2 <==
hello how are_you

==> file3 <==
    hello how are_you

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

$ 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]<>}