Объединить столбцы в файле на основе заголовка столбца

Объединить столбцы в файле на основе заголовка столбца

Я хочу объединить разные столбцы в моем файле, которые имеют одинаковый заголовок столбца. Файл выглядит так и может быть разделен табуляцией или как-то еще:

AaBbN    CcDdEeN    FfN     AaBbN    FfN
1        5          4   
3        1          2
2        NA         1
                            1        3
                            3        2
                            NA       4

Итак, в полях есть числа или строка "NA". Результат будет выглядеть так:

AaBbN    CcDdEeN    FfN
1        5          4
3        1          2
2        NA         1
1                   3
3                   2
NA                  4

Есть много столбцов, которые не упорядочены, поэтому заголовки заголовков должны быть прочитаны автоматически, а не вручную указаны все. Также есть много пустых полей. Я изучал команды pasteи joinдля выполнения этой работы. Особенно joinпохоже, что делает то, что мне нужно, за исключением того, что он работает с отдельными файлами, тогда как мои столбцы находятся в одном файле.

Поэтому я попытался разделить столбцы на отдельные файлы, а затем объединить их с помощью join. Я использовал awkкоманду, которую я вывел отсюда:

https://www.unix.com/unix-for-dummies-questions-and-answers/204303-splitting-up-text-file-into-multiple-files-columns.html

awk ' { for( i = 1; i <= NF; i++ ) printf( "%s\n", $(i) ) >i ".txt"; } ' file.txt

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

Моя вторая проблема join: Когда я пытаюсь снова объединить файлы, я получаю ошибки, потому что входные данные не отсортированы, что, конечно, невозможно сделать. Любая сортировка разрушит отношения, за которыми я слежу.

Вот я и в тупике. Есть ли более удобный способ объединить столбцы прямо в файле?


Редактировать:

Решение AdminBees ближе всего к решению проблемы, но результат не совсем правильный. Вот результат скрипта awk, примененного к примеру выше. Я убедился, что все записи разделены табуляцией sed -i "s/[[:space:]]/ /g"(табуляцию вставил с помощью CTRL+V и TAB).

AaBbN   CcDdEeN FfN     FfN
1   5   4   

3   1   2

2   NA  1

            1
            3
            NA

решение1

Если вводимые данные разделены табуляцией:

awk -F"\t" '
NR == 1 {for (i=1; i<=NF; i++)  COL[i] = $i
        }
        {for (i=1; i<=NF; i++) OUT[NR, COL[i]] = $i
        }
END     {for (n=1; n<=NR; n++)  {split ("", DUP)
                                 for (i=1; i<=NF; i++)  if (!DUP[COL[i]]++) printf "%s" FS, OUT[n,COL[i]]
                                 printf RS
                                }
        }
' file
A   B   C   
1   5   4   
3   1   2   
2   2   1   
1       3   
3       2   
1       4   

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

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

решение2

для ввода с разделением табуляцией.

считываем заголовок и соответствующие номера столбцов в массив, в котором они появились во входном файле; затем разделяем входной файл по каждому столбцу на файлы с одинаковым именем headerName.txt, имеющие одинаковые headerName. после этого вставляем их вместе иcolumnкоманда, используемая для улучшения вывода.

awk -F'\t' '
    ## find all the column number(s) when same header found and store in `h` array
    ## key is the column number and value is header name. for an example:
    ## for the header value 'A', keys will be columns 1 &4
    NR==1{ while (++i<=NF) h[i]=$i; next; }

         { for (i=1; i<=NF; i++) {

    ## save the field content to a file which its key column matches with the column 
    ## number of the current field. for an example:
    ## for the first field in column 1; the column number is 1, and so 1 is the key  
    ## column for header value A, so this will be written to "A.txt" filename
    ## only if it was not empty.
               if ($i!=""){ print $i> h[i]".txt" };
         }; }

    ## at the end paste those all files and beautify output with `column` command.
    ## number of .txt files above is limit to the number of uniq headers in your input. 
END{ system("paste *.txt |column \011 -tn") }' infile

команда без комментариев:

awk -F'\t' '
    NR==1{ while (++i<=NF) h[i]=$i; next; }
         { for (i=1; i<=NF; i++) {
               if ($i!=""){ print $i> h[i]".txt" };
         }; }
END{ system("paste *.txt |column \011 -tn") }' infile

решение3

Немного другой подход, не требующий «буферизации» всего файла:

Скрипт AWK colmerge.awk:

FNR==1{
    for (i=1; i<=NF; i++)
    {
    hdr[i]=$i;
    if (map[$i]==0) {map[$i]=i; uniq_hdr[++u]=$i; printf("%s",$i);}
    if (i==NF) printf("%s",ORS); else printf("%s",OFS);
    }
}

FNR>1{
    delete linemap;
    for (i=1; i<=NF; i++) if ($i!="") linemap[hdr[i]]=$i;
    for (i=1; i<=u; i++)
    {
    printf("%s",linemap[uniq_hdr[i]]);
    if (i==u) printf("%s",ORS); else printf("%s",OFS);
    }
}

Использовать как

awk -F'\t' -v OFS='\t' -f colmerge.awk file

Это позволит собрать все заголовки и определить «уникальные» заголовки и их первое появление в строке 1, а также для каждой последующей строки создать карту между заголовками и непустыми значениями, которая затем выводится в порядке «уникальных» заголовков, определенных при обработке первой строки.

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

Обратите внимание, что deleteоператор для всего массива linemapможет не поддерживаться всеми awkреализациями (однако должен работать в gawk, mawkи nawk).

Связанный контент