AWK:我如何知道列从哪里开始

AWK:我如何知道列从哪里开始

解析输入行后,awk 提供对原始行 ( $0) 以及每个单独列 ( $1, $2, ...) 的访问。在执行此过程时(惰性地,按需) - 它确切地知道第二列开始的字符位置。

  1. 它是否提供对此信息的访问(即,第二列从原始行 $0 中的哪个位置开始)?
  2. 如果没有 - 有什么理智/优雅的方法可以正确找到它吗? (我即将开始编写一种丑陋且低效的方式来模仿 awk 的内部行为,方法是使用基于 的动态正则表达式FS、处理特殊FS==" "情况、使用捕获组等。但在深入研究之前需要您的建议。)

示例 1(默认 FS):

$ echo -n -e " \tFirst \t\t  Second \t Third  \t"\
|awk -F" " '{print "FS:["FS"]";for(i=0;i<=5;i++)if(""!=$i)print "$"i":["$i"]"}'\
|sed 's/\t/\\t/g'

FS:[ ]
$0:[ \tFirst \t\t  Second \t Third  \t]
$1:[First]
$2:[Second]
$3:[Third]

在这里 - 我需要知道第二列 ( Second) 以字母开头S,这是13号输入行中的字符(这样我就可以存储First为键,并保留/存储Second \t Third \t完整的值作为进一步使用的值)


示例 2(TAB 作为 FS):

$ echo -n -e " \tFirst \t\t  Second \t Third  \t"\
|awk -F"\t" '{print "FS:["FS"]";for(i=0;i<=5;i++)if(""!=$i)print "$"i":["$i"]"}'\
|sed 's/\t/\\t/g'

FS:[\t]
$0:[ \tFirst \t\t  Second \t Third  \t]
$1:[ ]
$2:[First ]
$4:[  Second ]
$5:[ Third  ]

在这里 - 我需要知道第二列 ( First) 以字母开头F,这是第三名输入行中的字符 - 所以我能够将(空格)存储为键,并First \t\t Second \t Third \t完整保留/存储为进一步使用的值


示例 3(自定义 FS):

$ echo -n -e " \tFirst \t\t  Second \t Third  \t"\
|awk -F"[ \t]+" '{print "FS:["FS"]";for(i=0;i<=5;i++)if(""!=$i)print "$"i":["$i"]"}'\
|sed 's/\t/\\t/g'

FS:[[ \t]+]
$0:[ \tFirst \t\t  Second \t Third  \t]
$2:[First]
$3:[Second]
$4:[Third]

在这里 - 我需要知道第二列 ( First) 以字母开头F,这是第三名输入行中的字符 - 所以我知道第一列是空字符串,并将其存储First \t\t Second \t Third \t为值以供进一步使用


示例 4(复杂 FS):

$ echo "-11...22;,;..;33-44...;"\
|awk -F"[^0-9-]+" '{print "FS:["FS"]";for(i=0;i<=5;i++)if(""!=$i)print "$"i":["$i"]"}'

FS:[[^0-9-]+]
$0:[-11...22;,;..;33-44...;]
$1:[-11]
$2:[22]
$3:[33-44]

在这里 - 我需要知道第二列 ( 22) 以字符开头2,这是7号输入行中的字符 - 这样我就可以存储-11为键和22;,;..;33-44...;值以供进一步使用


基本上,这个想法是获取一些(第一)列以供自定义使用,并完整保留(存储到变量中)该行的其余部分(从第二列到行尾)。

答案1

使用 GNU awk 作为 split() 的第四个参数:

$ cat tst.awk
{
    split($0,flds,FS,seps)
    key = flds[1]
    pos = length(seps[0] flds[1] seps[1]) + 1
    val = substr($0,pos)
    printf "key=<%s>\npos=<%s>\nval=<%s>\n\n", key, pos, val
}

$ printf -- ' \tFirst \t\t  Second \t Third  \t\n' | awk -f tst.awk
key=<First>
pos=<13>
val=<Second      Third          >

$ printf -- '-11...22;,;..;33-44...;\n' | awk -F'[^0-9-]+' -f tst.awk
key=<-11>
pos=<7>
val=<22;,;..;33-44...;>

答案2

在 GNU/awk 中,您可以使用split()可选seps参数,然后迭代array和为每个字段seps生成startsize数组,累加每个字段和分隔符的长度。

此代码适用于 GNU/awk。函数 Offsets() 接受文本字符串和字段分隔符的模式,并返回一对包含起始列和字段长度的数组。

$ cat myCols
#! /bin/bash

myCols () {

    local Awk='
BEGIN { cmdDu = "od -A n -t a"; }
#.. Debug the input.
function Debug (tx, Local ) {
    printf ("\nLine %2d: %s\n", NR, tx);
    printf ("%s", tx) | cmdDu; close (cmdDu);
}
#.. Return arrays of column start and length.
function Offsets (col, lth, tx, re, Local, fld, sep, f) {
    delete col; delete lth;
    split (tx, fld, re, sep);
    c = length (sep[0]);
    for (f = 1; f in fld; ++f) {
        col[f] = 1 + c; lth[f] = length (fld[f]);
        c += length (fld[f]) + length (sep[f]);
    }
}
#.. Find fields and show the results.
function Fields (tx, re, Local, col, lth, f) {
    Offsets( col, lth, tx, re);
    for (f = 1; f in col; ++f) {
        printf ("Field %d col %3d lth %3d >%s<\n",
            f, col[f], lth[f], substr (tx, col[f], lth [f]));
    }
}
{ Debug( $0); }
NR == 1 { Fields( $0, ",[[:space:]]*"); }
NR == 2 { Fields( $0, FS); }
'
    awk -f <( printf '%s' "${Awk}" )

}

    {
        echo "Never,   Ever,  Forget,  but maybe,   Forgive"
        echo -n -e " \tFirst \t\t  Second \t Third  \t"
    } | myCols

并测试一下:

$ ./myCols

Line  1: Never,   Ever,  Forget,  but maybe,   Forgive
   N   e   v   e   r   ,  sp  sp  sp   E   v   e   r   ,  sp  sp
   F   o   r   g   e   t   ,  sp  sp   b   u   t  sp   m   a   y
   b   e   ,  sp  sp  sp   F   o   r   g   i   v   e
Field 1 col   1 lth   5 >Never<
Field 2 col  10 lth   4 >Ever<
Field 3 col  17 lth   6 >Forget<
Field 4 col  26 lth   9 >but maybe<
Field 5 col  39 lth   7 >Forgive<

Line  2:    First         Second     Third      
  sp  ht   F   i   r   s   t  sp  ht  ht  sp  sp   S   e   c   o
   n   d  sp  ht  sp   T   h   i   r   d  sp  sp  ht
Field 1 col   3 lth   5 >First<
Field 2 col  13 lth   6 >Second<
Field 3 col  22 lth   5 >Third<
$ 

如果您想要从(例如)第三个字段开始的确切原始字符串,请使用:

if (3 in col) Tail = substr (tx, col[3]);

答案3

如果您总是要使用$1作为键,并将该行的其余部分(来自$2)用作值,那么您可以使用来index查找它们所在的位置$0

  1. 用于获取inindex的位置$1$2
  2. 然后使用 的 长度来获取第一个位置 where can$1的子字符串(对于can 包含 的副本的情况)$0$2$1$2
  3. 然后index再次使用来获取的位置$2,这样我们就可以提取从开始的子字符串$2

例子:

# foo.awk
function mysplit(array) {
    pos1 = index($0, $1)
    sub1 = substr($0, pos1 + length($1))
    pos2 = index(sub1, $2)
    sub2 = substr(sub1, pos2)
    array[$1] = sub2
}

{mysplit(arr)}

END {
    for (i in arr) {
        printf "[%s]:   |%s|\n", i, arr[i]
    }
}

用你的例子:

% echo -n -e " \tFirst \t\t  Second \t Third  \t" | awk -f foo.awk -F " " | sed 's/\t/\\t/g'
[First]:   |Second \t Third  \t|
% echo -n -e " \tFirst \t\t  Second \t Third  \t" | awk -f foo.awk -F "\t" | sed 's/\t/\\t/g'
[ ]:   |First \t\t  Second \t Third  \t|
% echo -n -e " \tFirst \t\t  Second \t Third  \t" | awk -f foo.awk -F "[ \t]+" | sed 's/\t/\\t/g'
[]:   |First \t\t  Second \t Third  \t|

相关内容