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