Você quer essencialmente criar uma matriz bidimensional de valores. A primeira coluna em cada linha corresponde a uma chave , retirada do primeiro campo delimitado por tabulações em cada linha em cada arquivo de entrada. Cada coluna a seguir corresponde a um arquivo de entrada separado.
awk 'BEGIN {
RS = "(\r\n|\n\r|\r|\n)"
FS = " *\t *"
SUBSEP = ":"
}
FNR==1 {
++file
}
NF>=2 {
if ($1 in keynum)
key = keynum[$1]
else {
key = ++keys
keynum[$1] = key
keystr[key] = $1
}
value[key,file] = $2
}
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
printf "\t%s", value[key,file]
printf "\n"
}
}' INPUT1 INPUT2 ... INPUTN
A regra BEGIN
define o separador de registro para qualquer tipo de nova linha, para que cada linha seja um registro separado. Ele também define o separador de campo para uma guia, incluindo todos os espaços ao redor dele.
No awk, todos os arrays são associativos e basicamente unidimensionais. Arrays multidimensionais são suportados pela concatenação dos índices, com um SUBSEP
entre eles. Aqui, usamos :
como um separador, porque os índices usados são inteiros positivos. (Você poderia usar muitos outros caracteres se quisesse; por exemplo, uma aba \t
.)
A regra FNR==1
é acionada na primeira linha de cada arquivo de entrada. Incrementamos a variável file
, de modo que seja 1
para o primeiro arquivo de entrada, 2
para o segundo e assim por diante.
A regra NF>=2
é acionada para todos os registros com pelo menos dois campos. Nesse caso, significa para cada linha que possui um caractere de tabulação. O primeiro campo é a chave e o segundo campo o valor .
A variável key
é um inteiro positivo, referindo-se a uma string de chave exclusiva. (1 se refere à primeira única chave vista, 2 à segunda e assim por diante, em todos os arquivos de entrada.)
A matriz associativa keynum
mapeia cadeias-chave para números-chave ( key
, inteiros positivos). O keystr
é o mapeamento inverso, mapeando números de chaves para cadeias de caracteres de chave.
Na regra NF>=2
, se o primeiro campo já é uma chave conhecida, seu número é procurado. Caso contrário, o primeiro campo é adicionado como uma nova string de chave exclusiva. Em seguida, o segundo campo é salvo na matriz value
.
A regra END
é acionada após todos os arquivos de entrada terem sido processados. A matriz value
, uma matriz logicamente bidimensional, contém os campos que queremos.
O loop externo faz um loop key
sobre todas as chaves exclusivas vistas, na ordem em que foram vistas pela primeira vez. Cada iteração do loop externo produz uma linha de saída.
O loop interno faz um loop de file
sobre cada arquivo de entrada, na ordem em que foram listados. Cada iteração produz uma coluna adicional para a saída da linha atual. Cada linha de saída contém exatamente mais uma coluna do que o número de arquivos de entrada especificados. (Observe que, se nenhum arquivo de entrada for especificado, o awk será lido a partir da entrada padrão e será contado como se fosse um arquivo de entrada.)
Este definitivamente não é o método mais simples para conseguir isso, mas eu gosto disso, porque é robusto (aceita arquivos de entrada criados em Unix, Linux, Macs antigos, novos Macs, Windows - basicamente em qualquer lugar que use um compatível com ASCII conjunto de caracteres; também não se confundirá se alguns arquivos de entrada tiverem apenas um subconjunto de todas as chaves conhecidas), relativamente fáceis de entender, manter e se adaptar a casos semelhantes.
Se você deseja executar o código acima como um script, salve o seguinte como, por exemplo, paste.awk
:
#!/usr/bin/awk -f
BEGIN {
RS = "(\r\n|\n\r|\r|\n)"
FS = " *\t *"
SUBSEP = ":"
}
FNR==1 {
++file
}
NF>=2 {
if ($1 in keynum)
key = keynum[$1]
else {
key = ++keys
keynum[$1] = key
keystr[key] = $1
}
printf "key = %s, file = %s, value = %s\n", key, file, $2 >/dev/stderr
value[key,file] = $2
}
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
printf "\t%s", value[key,file]
printf "\n"
}
}
Se você tiver input1
contendo
a
A 5
C 4
D 2
e input2
contendo
b
A 2
B 5
C 3
e input3
contendo
c
B 4
C 4
D 2
mas com o segundo caractere em cada linha sendo Tab ; isto é, criado usando, por exemplo,
printf ' \ta\nA\t5\nC\t4\nD\t2\n' > input1
printf ' \tb\nA\t2\nB\t5\nC\t3\n' > input2
printf ' \tc\nB\t4\nC\t4\nD\t2\n' > input3
ou, se você copiar e colar o texto acima em arquivos, execute sed -e 's|^\(.\) *|\t|' -i input1 input2 input3
para corrigi-los; então, correndo
paste.awk input1 input2 input3
saídas
a b c
A 5 2
C 4 3 4
D 2 2
B 5 4
exceto que os espaços consecutivos acima são realmente tab s. O software neste site converte guias em espaços, você vê.
Editado para adicionar: Se você quiser usar algum valor predefinido para entradas ausentes, modifique a regra END
em
END {
files = file
for (key = 1; key <= keys; key++) {
printf "%s", keystr[key]
for (file = 1; file <= files; file++)
if ((key SUBSEP file) in value)
printf "\t%s", value[key,file]
else
printf "\t%s", blank
printf "\n"
}
}
e defina a variável blank
para refletir o valor desejado. (Você pode defini-lo a partir da linha de comando, usando ./paste.awk -v blank=0 input1 input2 input3
ou modificar o código awk e definir o valor em algum lugar na regra BEGIN
ou no início da regra END
.)