Comparação lado a lado de mais de dois arquivos contendo valores numéricos

8

Eu tenho três arquivos contendo uma seqüência ordenada de números, um por linha:

file1

1
2
3

file2

1
3
4

file3

1
5

Eu quero "alinhar" esses três arquivos lado a lado da seguinte forma:

file1  file2  file3
1      1      1
2      
3      3
       4
              5

Eu tentei com sdiff , mas ele só funciona com 2 arquivos

    
por cheseaux 11.07.2016 / 14:49

3 respostas

6

Você pode processar cada arquivo e imprimir uma linha com algum caractere. X para cada número ausente na sequência 1- max (onde max é o último número nesse arquivo), paste os resultados substituem o caractere por espaço :

paste \
<(awk 'BEGIN{n=1};{while (n<$1) {print "X";n++}};{n=$1+1};1' file1) \
<(awk 'BEGIN{n=1};{while (n<$1) {print "X";n++}};{n=$1+1};1' file2) \
<(awk 'BEGIN{n=1};{while (n<$1) {print "X";n++}};{n=$1+1};1' file3) \
| tr X ' '

Se um certo valor estiver faltando em todos os arquivos, você obterá linhas vazias em sua saída (na verdade, elas não estão vazias, elas contêm apenas espaços em branco). Para removê-los, substitua tr X ' ' por sed '/[[:digit:]]/!d;s/X/ /g' Além disso, se você precisar de um cabeçalho, sempre poderá executar algo assim primeiro:

 printf '\t%s' file1 file2 file3 | cut -c2-
    
por 11.07.2016 / 16:38
5

Uma solução geral com o awk: requer o GNU awk

gawk -v level=0 '
    FNR==1 {level++; head[level]=FILENAME}
    !seen[$1]++ { n++; idx[$1] = n }
    { out[idx[$1]][level] = $1 }
    END {
        for (j=1; j<=level; j++) {
            printf "%s\t", head[j]
        }
        print ""
        for (i=1; i<=n; i++) {
            for (j=1; j<=level; j++) {
                printf "%s\t", out[i][j]
            }
            print ""
        }
    }
' file{1,2,3,4}
file1   file2   file3   file4   
1   1   1       
2           2   
3   3           
    4       4   
        5       
            6   

Tomou uma abordagem diferente e mais simples para isso com base no comentário de Don:

gawk '
    FNR==1 { printf "%s\t", FILENAME }
    { seen[$1][FILENAME] = $1 } 
    END {
        print ""
        PROCINFO["sorted_in"]="@ind_num_asc"
        for (i in seen) {
            for (j=1; j<=ARGC; j++) {
                printf "%s\t", seen[i][ARGV[j]]
            } 
            print ""
        }
    }
' file{1,2,3,4}
file1   file2   file3   file4       
    1   1           
            2       
3   3               
    4       4       
5       5           
            6       
7                   
    
por 11.07.2016 / 16:39
3

Uma solução com bash , join , paste e gosto ruim:

#! /usr/bin/env bash

if [ $# -lt 3 ]; then exit 1; fi

files=( '' "$@" )

declare -a temps
for ((i=0; i<=$#; i++)); do
    [ $i -eq 0 -o -f "${files[$i]}" ] || exit 1
    temps[$i]=$( mktemp -t "${0##*/}"_$$_XXXXXXXX ) || exit 1
done
trap 'rm -f "${temps[@]}"' EXIT HUP INT QUIT TERM

cat "$@" | sort -u >"${temps[0]}"

TAB=$( printf '\t' )
for ((i=1; i<=$#; i++)); do
    join -j1 -a1 -t"$TAB" "${temps[0]}" <(paste "${files[$i]}" "${files[$i]}") | \
        sed "/^[^$TAB]\$/ s/\$/$TAB/" >"${temps[$i]}"
done

printf '%s' ${files[1]}
for ((i=2; i<=$#; i++)); do
    printf '\t%s' ${files[$i]}
    let j=i-1
    let k=i-2
    join -j1 -t"$TAB" "${temps[$j]}" "${temps[$i]}" >"${temps[$k]}"
    cat "${temps[$k]}" >"${temps[$i]}"
done
printf '\n'

cut -d "$TAB" -f 2- <"${temps[$#]}" | sort -n

Com exceção do último sort -n , tudo isso funciona com qualquer item de texto em vez de números, contanto que os itens não contenham guias (mas TAB pode ser alterado para qualquer outro separador). Além disso, isso pode ser feito com apenas 3 arquivos temporários e algumas coisas aleatórias (mas isso apenas aumentaria o gosto ruim).

    
por 11.07.2016 / 19:00