Разделение большого сложного файла с одним столбцом на несколько столбцов с помощью awk - PullRequest
0 голосов
/ 10 декабря 2018

У меня есть текстовый файл, созданный коммерческим программным обеспечением, как показано ниже.Он состоит из разделенных скобками разделов, каждый из которых насчитывает несколько миллионов элементов, но точное значение изменяется от одного случая к другому.

(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 , но оно предполагает постоянное количество строк между скобками, что я не могу сделать.

Буду признателен за любую помощь.

Ответы [ 6 ]

0 голосов
/ 25 декабря 2018

Вот 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
0 голосов
/ 10 декабря 2018

Я бы преобразовал каждый раздел в строку, а затем транспонировал после, например, предполагая, что вы используете GNU awk:

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

Вывод:

1;11;111
2;22;222
3;33;333
...;...;...
0 голосов
/ 10 декабря 2018

С 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
   ...;   ...;   ...
0 голосов
/ 10 декабря 2018
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

output

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

Измените FS= "\\s*" на FS = "\n*", если вы также хотите разрешить пробелы внутри ваших полей.

Этот скрипт поддерживает столбцы различной длины.

При тестировании также подумайте о замене [i,j] на [i][j] для GNU awk.Я не уверен, какой из них быстрее, и сам не тестировал скрипт.

0 голосов
/ 10 декабря 2018

Не могли бы вы попробовать выполнить один раз, учитывая, что ваш фактический 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="; "

ИЛИ (приведенный выше скрипт учитывает, что числа внутри (...) будутконстанта, теперь добавляется скрипт, который будет работать с четными номерами полей, не равными внутри (....).

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 значения в скобках (...).

0 голосов
/ 10 декабря 2018

Если вы знаете, что у вас есть 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>
...