是否有一个字段存储在正则表达式中使用的确切字段分隔符 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 个参数中使用的正则表达式存储匹配文本的数组,即 /[;|]/
.
当然不像RS
、ORS
、RT
那么简单,可以写成:
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)
}
}
请注意上面对字段分隔符为 " "
的情况的具体处理,因为这意味着与所有其他字段分隔符值有两点不同:
- 字段实际上由任何白色的链分隔 space,和
- 前导白色 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]<>}
在GNU Awk's 4.1.2 Record Splitting with gawk
中我们可以读到:
When
RS
is a single character,RT
contains the same single character. However, whenRS
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 个参数中使用的正则表达式存储匹配文本的数组,即 /[;|]/
.
当然不像RS
、ORS
、RT
那么简单,可以写成:
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"
作为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)
}
}
请注意上面对字段分隔符为 " "
的情况的具体处理,因为这意味着与所有其他字段分隔符值有两点不同:
- 字段实际上由任何白色的链分隔 space,和
- 前导白色 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]<>}