Quero mesclar diferentes colunas em meu arquivo que compartilham o mesmo cabeçalho de coluna. O arquivo se parece com isto e pode ser separado por tabulações ou outra coisa:
AaBbN CcDdEeN FfN AaBbN FfN
1 5 4
3 1 2
2 NA 1
1 3
3 2
NA 4
Portanto, existem números ou a string “NA” nos campos. O resultado ficaria assim:
AaBbN CcDdEeN FfN
1 5 4
3 1 2
2 NA 1
1 3
3 2
NA 4
Há muitas colunas que não estão ordenadas, portanto, os cabeçalhos dos títulos precisariam ser lidos automaticamente, em vez de especificar manualmente cada uma delas. Também existem muitos campos vazios. Estive pesquisando os comandos paste
e join
para fazer o trabalho. Especialmente join
parece fazer o que preciso, exceto que funciona com arquivos separados, enquanto minhas colunas estão dentro do mesmo arquivo.
Então tentei separar as colunas em arquivos separados e depois combiná-los com arquivos join
. Usei um awk
comando derivado daqui:
awk ' { for( i = 1; i <= NF; i++ ) printf( "%s\n", $(i) ) >i ".txt"; } ' file.txt
o que me dá colunas separadas, mas aqui me deparei com o primeiro problema. Todas as colunas com espaço vazio entre o cabeçalho e os dados não foram processadas corretamente. Em vez disso, apenas o cabeçalho da coluna estava presente nesses arquivos.
Meu segundo problema é join
: quando tento mesclar os arquivos novamente, recebo erros porque a entrada não está classificada, o que é obviamente impossível de fazer. Qualquer classificação destruiria o relacionamento que estou cuidando.
Então aqui estou eu em um beco sem saída. Existe uma maneira mais conveniente de mesclar as colunas diretamente em um arquivo?
Editar:
A solução AdminBees é a que mais se aproxima de resolver o problema, mas o resultado não é muito bom. Aqui está o resultado do script awk aplicado ao exemplo acima. Certifiquei-me de que todas as entradas fossem separadas por tabulações sed -i "s/[[:space:]]/ /g"
(tabulação inserida com CTRL+V e TAB).
AaBbN CcDdEeN FfN FfN
1 5 4
3 1 2
2 NA 1
1
3
NA
Responder1
Se sua entrada for separada por tabulações:
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
Ele salva os cabeçalhos das colunas para uso posterior como índices parciais e, para cada linha, coleta valores em uma matriz indexada pelo número da linha e pelo índice parcial do cabeçalho. Na END
seção, ele imprime esse array na sequência original, cuidando dos cabeçalhos de coluna duplicados.
O tratamento duplicado pode se tornar um grande esforço para estruturas de arquivos mais complexas.
Responder2
para a entrada separada por tabulações.
ler o cabeçalho e os números das colunas correspondentes em um array onde eles apareceram no arquivo de entrada; em seguida, dividir o arquivo de entrada em cada coluna no mesmo nome de arquivo headerName.txt com o mesmo headerName. afinal, cole-os juntos ecolumn
comando usado para embelezar a saída.
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
comando sem comentários:
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
Responder3
Uma abordagem um pouco diferente que não requer "armazenamento em buffer" de todo o arquivo:
Roteiro 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);
}
}
Usar como
awk -F'\t' -v OFS='\t' -f colmerge.awk file
Isso reunirá todos os cabeçalhos e identificará os cabeçalhos "únicos" e sua primeira ocorrência na linha 1, e para cada linha sucessiva criará um mapa entre cabeçalhos e valores não vazios, que será então impresso na ordem dos cabeçalhos "únicos" conforme identificado durante o processamento da primeira linha.
No entanto, isso só funciona se o seu arquivo de entrada estiver separado por tabulações, pois essa é a única maneira de detectar campos "vazios" com segurança.
Observe também que a delete
instrução para todo o array linemap
pode não ser suportada por todas awk
as implementações (deve funcionar em e gawk
, no entanto).mawk
nawk