使用 awk 将一个大型、复杂的一列文件拆分为几列



>我有一个由一些商业软件生成的文本文件,如下所示。它由括号分隔的部分组成,每个部分计数数百万个元素,但确切的值因情况而异。

(1
2
3
...
)
(11
22
33
...
)
(111
222
333
...
)

我需要实现如下输出:

1;  11;   111
2;  22;   222
3;  33;   333
...  ...  ...

我发现了一个复杂的方法,即:

  • 执行 SED 操作以获取

    1
    2
    3
    ...
    #
    11
    22
    33
    ...
    #
    111
    222
    333
    ...
    
  • 按如下方式使用 awk 将我的文件拆分为多个子文件

    awk -v RS="#" '{print > ("splitted-" NR ".txt")}'
    
  • 使用 SED 再次从我的子文件中删除空格

    sed -i '/^[[:space:]]*$/d' splitted*.txt
    
  • 将所有内容连接在一起:

    paste splitted*.txt > out.txt
    
  • 添加字段分隔符(在我的 bash 脚本中定义)

    awk -v sep=$my_sep 'BEGIN{OFS=sep}{$1=$1; print }' out.txt > formatted.txt
    

我觉得这很糟糕,因为我多次循环超过百万行。 即使返回时间还可以(~80秒),我也想找到一个完整的awk解决方案,但无法实现。 像这样:

awk 'BEGIN{RS="(\n)"; OFS=";"} { print something } '

我发现了一些相关的问题,尤其是这个带有awk的一行到列的转换,但它假设括号之间的行数恒定,我做不到。

任何帮助将不胜感激。

使用 GNU awk 用于多字符 RS 和真正的多维数组:

$ cat tst.awk
BEGIN {
RS  = "(\s*[()]\s*)+"
OFS = ";"
}
NR>1 {
cell[NR][1]
split($0,cell[NR])
}
END {
for (rowNr=1; rowNr<=NF; rowNr++) {
for (colNr=2; colNr<=NR; colNr++) {
printf "%6s%s", cell[colNr][rowNr], (colNr<NR ? OFS : ORS)
}
}
}
$ awk -f tst.awk file
1;    11;   111
2;    22;   222
3;    33;   333
...;   ...;   ...

如果你知道你有 3 列,你可以用非常丑陋的方式做,如下所示:

pr -3ts <file>

然后需要做的就是删除括号:

$ pr -3ts ~/tmp/f | awk 'BEGIN{OFS="; "}{gsub(/[()]/,"")}(NF){$1=$1; print}'
1; 11; 111
2; 22; 222
3; 33; 333
...; ...; ...

你也可以在一条awk行中做到这一点,但它只会使事情复杂化。以上是快速和简单的。

这个awk程序执行完整的通用版本:

awk 'BEGIN{r=c=0}
/)/{r=0; c++; next}
{gsub(/[( ]/,"")}
(NF){a[r++,c]=$1; rm=rm>r?rm:r}
END{ for(i=0;i<rm;++i) {
printf a[i,0];
for(j=1;j<c;++j) printf "; " a[i,j];
print ""
}
}' <file>

考虑到您的实际Input_file与所示样本相同,您能否尝试以下一次。

awk -v RS=""  '
{
gsub(/n|, /,",")
}
1' Input_file |
awk '
{
while(match($0,/([^)]*/)){
value=substr($0,RSTART+1,RLENGTH-2)
$0=substr($0,RSTART+RLENGTH)
num=split(value,array,",")
for(i=1;i<=num;i++){
val[i]=val[i]?val[i] OFS array[i]:array[i]
}
}
for(j=1;j<=num;j++){
print val[j]
}
delete val
delete array
value=""
}'   OFS="; "

OR(上面的脚本考虑到(...)内的数字将是恒定的,现在添加脚本,该脚本甚至可以在(....)内不相等的字段数字工作。

awk -v RS=""  '
{
gsub(/n/,",")
gsub(/, /,",")
}
1'  Input_file |
awk '
{
while(match($0,/([^)]*/)){
value=substr($0,RSTART+1,RLENGTH-2)
$0=substr($0,RSTART+RLENGTH)
num=split(value,array,",")
for(i=1;i<=num;i++){
val[i]=val[i]?val[i] OFS array[i]:array[i]
max=num>max?num:max
}
}
for(j=1;j<=max;j++){
print val[j]
}
delete val
delete array
}' OFS="; "

输出将如下所示。

1; 11; 111
2; 22; 222
3; 33; 333


说明:在此处添加上述代码的说明。

awk -v RS=""  '                                      ##Setting RS(record separator) as NULL here.
{                                                    ##Starting BLOCK here.
gsub(/n/,",")                                  ##using gsub to substitute new line OR comma with space with comma here.
gsub(/, /,",")
}
1' Input_file  |                                        ##Mentioning 1 will be printing edited/non-edited line of Input_file. Using | means sending this output as Input to next awk program.
awk '                                                ##Starting another awk program here.
{
while(match($0,/([^)]*/)){                       ##Using while loop which will run till a match is FOUND for (...) in lines.
value=substr($0,RSTART+1,RLENGTH-2)             ##storing substring from RSTART+1 to till RLENGTH-1 value to variable value here.
$0=substr($0,RSTART+RLENGTH)                    ##Re-creating current line with substring valeu from RSTART+RLENGTH till last of line.
num=split(value,array,",")                      ##Splitting value variable into array named array whose delimiter is comma here.
for(i=1;i<=num;i++){                            ##Using for loop which runs from i=1 to till value of num(length of array).
val[i]=val[i]?val[i] OFS array[i]:array[i]    ##Creating array val whose index is value of variable i and concatinating its own values.
}
}
for(j=1;j<=num;j++){                               ##Starting a for loop from j=1 to till value of num here.
print val[j]                                    ##Printing value of val whose index is j here.
}
delete val                                         ##Deleting val here.
delete array                                       ##Deleting array here.
value=""                                           ##Nullifying variable value here.
}'  OFS="; "                                         ##Making OFS value as ; with space here.

注意:这也应该适用于括号内的 3 个以上值(...)

awk 'BEGIN { RS = "\s*[()]\s*"; FS = "\s*" }
NF > 0 {
maxCol++
if (NF > maxRow)
maxRow = NF
for (row = 1; row <= NF; row++)
a[row,maxCol] = $row
}
END {
for (row = 1; row <= maxRow; row++) {
for (col = 1; col <= maxCol; col++)
printf "%s", a[row,col] ";"
print ""
}
}' yourFile

输出

1;11;111;
2;22;222;
3;33;333;
...;...;...;

当您还希望字段中留出空格时,将FS= "\s*"更改为FS = "n*"

此脚本支持不同长度的列。

在基准测试时,还要考虑将[i,j]替换为 GNUawk[i][j]。我不确定哪个更快,也没有自己对脚本进行基准测试。

这是 Perl 单行解决方案

$ cat edouard2.txt
(1
2
3
a
)
(11
22
33
b
)
(111
222
333
c
)
$ perl -lne ' $x=0 if s/[)(]// ; if(/(S+)/) { @t=@{$val[$x]};push(@t,$1);$val[$x++]=[@t] } END { print join(";",@{$val[$_]}) for(0..$#val) }' edouard2.txt
1;11;111
2;22;222
3;33;333
a;b;c

我会将每个部分转换为一行,然后在之后转置,例如假设您使用的是 GNU awk:

<infile awk '{ gsub("[( )]", ""); $1=$1 } 1' RS='\)n\(' OFS=';' |
datamash -t';' transpose

输出:

1;11;111
2;22;222
3;33;333
...;...;...

最新更新