combina arquivos de texto no modo de coluna

42

Eu tenho dois arquivos de texto. O primeiro tem conteúdo:

Languages
Recursively enumerable
Regular

enquanto o segundo tem conteúdo:

Minimal automaton
Turing machine
Finite

Eu quero combiná-los em um arquivo em coluna. Então eu tentei paste 1 2 e sua saída é:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

No entanto, gostaria de ter as colunas bem alinhadas, como

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Eu queria saber se seria possível conseguir isso sem manipular manualmente?

Adicionado:

Aqui está outro exemplo, onde o método de Bruce quase o une, exceto alguns ligeiros desalinhamentos sobre os quais eu me pergunto por quê?

$ cat 1
Chomsky hierarchy
Type-0
—

$ cat 2
Grammars
Unrestricted

$ paste 1 2 | pr -t -e20
Chomsky hierarchy   Grammars
Type-0              Unrestricted
—                    (no common name)
    
por Tim 11.07.2011 / 02:28

7 respostas

57

Você só precisa do comando column e diz para usar guias para separar colunas

paste file1 file2 | column -s $'\t' -t

Para resolver a controvérsia "célula vazia", só precisamos da opção -n para column :

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -t
foo        1
2
barbarbar  3

$ paste <(echo foo; echo; echo barbarbar) <(seq 3) | column -s $'\t' -tn
foo        1
           2
barbarbar  3

A página man my column indica -n é uma "extensão Debian GNU / Linux." Meu sistema Fedora não exibe o problema da célula vazia: ele parece ser derivado do BSD e a página man diz que "A versão 2.23 alterou a opção -s para não ser gananciosa"

    
por 11.07.2011 / 15:57
10

Você está procurando o prático comando dandy pr :

paste file1 file2 | pr -t -e24

O "-e24" é "expanda as paradas de tabulação para 24 espaços". Felizmente, paste coloca um caractere de tabulação entre colunas, portanto pr pode expandi-lo. Eu escolhi 24 contando os caracteres em "Recursivamente enumerável" e adicionando 2.

    
por 11.07.2011 / 03:26
9

Update : Aqui está um script muito mais simples (aquele no final da pergunta) para saída tabulada. Basta passar o nome do arquivo para ele como você faria para paste ... Ele usa html para criar o quadro, então ele é ajustável. Ele preserva vários espaços e o alinhamento da coluna é preservado quando encontra caracteres unicode. No entanto, a maneira como o editor ou visualizador renderiza o unicode é outra questão inteiramente ...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ Languages            │ Minimal        │ Chomsky  │ Unrestricted               │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Recursive            │ Turing machine │ Finite   │     space indented         │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ Regular              │ Grammars       │          │ ➀ unicode may render oddly │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│ 1 2  3   4    spaces │                │ Symbol-& │ but the column count is ok │
├──────────────────────┼────────────────┼──────────┼────────────────────────────┤
│                      │                │          │ Context                    │
└──────────────────────┴────────────────┴──────────┴────────────────────────────┘
#!/bin/bash
{ echo -e "<html>\n<table border=1 cellpadding=0 cellspacing=0>"
  paste "$@" |sed -re 's#(.*)#\x09\x09#' -e 's#\x09# </pre></td>\n<td><pre> #g' -e 's#^ </pre></td>#<tr>#' -e 's#\n<td><pre> $#\n</tr>#'
  echo -e "</table>\n</html>"
} |w3m -dump -T 'text/html'

---

Uma sinopse das ferramentas apresentadas nas respostas (até agora).
Eu tive uma boa olhada neles; aqui está o que eu encontrei:

paste # Esta ferramenta é comum a todas as respostas apresentadas até agora         # Pode lidar com vários arquivos; portanto, várias colunas ... Bom!         # Delimita cada coluna com um Tab ... Bom.         # Sua saída não está tabulada.

Todas as ferramentas abaixo removem este delimitador! ... Ruim se você precisar de um delimitador.

column # Remove o delimitador de tabulação, portanto a identificação do campo é puramente por colunas que parece lidar muito bem .. Eu não vi nada de errado ... # Além de não ter um delimitador único, ele funciona bem!

expand # Só tem uma configuração de tabulação única, portanto é imprevisível além de 2 colunas # O alinhamento de colunas não é preciso ao manusear unicode e remove o delimitador de tabulação, portanto a identificação de campo é puramente por alinhamento de coluna

pr # Só tem uma configuração de guia única, portanto, é imprevisível além de 2 colunas. # O alinhamento de colunas não é preciso ao manusear unicode, e remove o delimitador de tabulação, portanto, a identificação do campo é puramente por alinhamento de colunas

Para mim, column é a melhor solução óbvia como one-liner .. Você quer o delimitador, ou uma tabulação ASCII de seus arquivos, continue lendo, caso contrário .. columns é bem difícil bom:) ...

Aqui está um script que pega qualquer número de arquivos e cria uma apresentação tabulada em ASCII-art .. (lembre-se de que unicode pode não renderizar a largura esperada, por exemplo, ௵ que é um único caractere. Isso é bem diferente para os números de coluna estarem errados, como é o caso em alguns dos utilitários mencionados acima.) ... A saída do script, mostrada abaixo, é de 4 arquivos de entrada, chamados F1 F2 F3 F4 ...

+------------------------+-------------------+-------------------+--------------+
| Languages              | Minimal automaton | Chomsky hierarchy | Grammars     |
| Recursively enumerable | Turing machine    | Type-0            | Unrestricted |
| Regular                | Finite            | —                 |              |
| Alphabet               |                   | Symbol            |              |
|                        |                   |                   | Context      |
+------------------------+-------------------+-------------------+--------------+
#!/bin/bash

# Note: The next line is for testing purposes only!
set F1 F2 F3 F4 # Simulate commandline filename args $1 $2 etc...

p=' '                                # The pad character
# Get line and column stats
cc=${#@}; lmax=                      # Count of columns (== input files)
for c in $(seq 1 $cc) ;do            # Filenames from the commandline 
  F[$c]="${!c}"        
  wc=($(wc -l -L <${F[$c]}))         # File length and width of longest line 
  l[$c]=${wc[0]}                     # File length  (per file)
  L[$c]=${wc[1]}                     # Longest line (per file) 
  ((lmax<${l[$c]})) && lmax=${l[$c]} # Length of longest file
done
# Determine line-count deficits  of shorter files
for c in $(seq 1 $cc) ;do  
  ((${l[$c]}<lmax)) && D[$c]=$((lmax-${l[$c]})) || D[$c]=0 
done
# Build '\n' strings to cater for short-file deficits
for c in $(seq 1 $cc) ;do
  for n in $(seq 1 ${D[$c]}) ;do
    N[$c]=${N[$c]}$'\n'
  done
done
# Build the command to suit the number of input files
source=$(mktemp)
>"$source" echo 'paste \'
for c in $(seq 1 $cc) ;do
    ((${L[$c]}==0)) && e="x" || e=":a -e \"s/^.{0,$((${L[$c]}-1))}$/&$p/;ta\""
    >>"$source" echo '<(sed -re '"$e"' <(cat "${F['$c']}"; echo -n "${N['$c']}")) \'
done
# include the ASCII-art Table framework
>>"$source" echo ' | sed  -e "s/.*/| & |/" -e "s/\t/ | /g" \'   # Add vertical frame lines
>>"$source" echo ' | sed -re "1 {h;s/[^|]/-/g;s/\|/+/g;p;g}" \' # Add top and botom frame lines 
>>"$source" echo '        -e "$ {p;s/[^|]/-/g;s/\|/+/g}"'
>>"$source" echo  
# Run the code
source "$source"
rm     "$source"
exit

Aqui está minha resposta original (cortada um pouco em vez do script acima)

Usando wc para obter a largura da coluna e sed para a direita com um caracter visível . (apenas para este exemplo) ... e, em seguida, paste para ingressar as duas colunas com um Tab char ...

paste <(sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1) F2

# output (No trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine
Regular...............  Finite

Se você quiser preencher a coluna da direita:

paste <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F1)-1))"'}$/&./;ta' F1 ) \
      <( sed -re :a -e 's/^.{1,'"$(($(wc -L <F2)-1))"'}$/&./;ta' F2 )  

# output (With trailing whitespace)
Languages.............  Minimal automaton
Recursively enumerable  Turing machine...
Regular...............  Finite...........
    
por 11.07.2011 / 07:15
5

Você está quase lá. paste coloca um caractere de tabulação entre cada coluna, então tudo que você precisa fazer é expandir as guias. (Eu suponho que seus arquivos não contêm guias.) Você precisa determinar a largura da coluna da esquerda. Com utilitários GNU (recentes o suficiente), wc -L mostra o comprimento da linha mais longa. Em outros sistemas, faça uma primeira passagem com o awk. O +1 é a quantidade de espaço em branco que você deseja entre as colunas.

paste left.txt right.txt | expand -t $(($(wc -L <left.txt) + 1))
paste left.txt right.txt | expand -t $(awk 'n<length {n=length} END {print n+1}')

Se você tiver o utilitário de coluna BSD, poderá usá-lo para determinar a largura da coluna e expandir as guias de uma só vez. ( é um caractere de tabulação literal; no bash / ksh / zsh você pode usar $'\t' , e em qualquer shell você pode usar "$(printf '\t')" .)

paste left.txt right.txt | column -s '␉' -t
    
por 11.07.2011 / 18:07
4

Isso é multi-passo, então não é ideal, mas aqui vai.

1) Encontre o comprimento da linha mais longa em file1.txt .

while read line
do
echo ${#line}
done < file1.txt | sort -n | tail -1

Com o seu exemplo, a linha mais longa é 22.

2) Use o awk para preencher file1.txt , preenchendo cada linha com menos de 22 caracteres até 22 com a instrução printf .

awk 'FS="---" {printf "%-22s\n", $1}' < file1.txt > file1-pad.txt

Nota: para o FS, use uma string que não exista em file1.txt .

3) Use colar como você fez antes.

$ paste file1-pad.txt file2.txt
Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Se isso é algo que você faz com frequência, isso pode ser facilmente transformado em um script.

    
por 11.07.2011 / 03:22
4

Eu não posso comentar sobre a resposta de Glenn Jackman, então estou adicionando isso para resolver o problema das células vazias que Peter.O notou. Adicionar um caractere nulo antes de cada guia elimina as execuções de delimitadores que são tratadas como uma única quebra e resolvem o problema. (Eu originalmente usei espaços, mas usando o caractere nulo elimina o espaço extra entre colunas).

paste file1 file2 | sed 's/\t/
paste file1 file2 | sed 's/\t/ \t/g' | column -s $'\t' -t
\t/g' | column -s $'\t' -t

Se o caractere nulo causar problemas por várias razões, tente:

paste file1 file2 | sed $'s/\t/ \t/g' | column -s $'\t' -t

ou

paste file1 file2 | sed 's/\t/
paste file1 file2 | sed 's/\t/ \t/g' | column -s $'\t' -t
\t/g' | column -s $'\t' -t

Tanto o sed como o column parecem variar na implementação em versões e versões do Unix / Linux, especialmente BSD (e Mac OS X) versus GNU / Linux.

    
por 05.11.2014 / 22:20
0

Aproveitando a resposta da bahamat : isso pode ser feito inteiramente em awk , ler os arquivos apenas uma vez e não criar arquivos temporários. Para resolver o problema como declarado, faça

awk '
        NR==FNR { if (length > max_length) max_length = length
                  max_FNR = FNR
                  save[FNR] = $0
                  next
                }
                { printf "%-*s", max_length+2, save[FNR]
                  print
                }
        END     { if (FNR < max_FNR) {
                        for (i=FNR+1; i <= max_FNR; i++) print save[i]
                  }
                }
    '   file1 file2

Tal como acontece com muitos scripts awk desta laia, o texto acima lê primeiro file1 , salvando todos os dados no array save e simultaneamente calcular o comprimento máximo da linha. Então ele lê file2 e imprime os dados salvos ( file1 ) lado a lado com os dados atuais ( file2 ). Finalmente, se file1 for maior que file2 (tem mais linhas), nós imprimimos as últimas linhas de file1 (aqueles para os quais não há linha correspondente na segunda coluna).

Em relação ao formato printf :

  • "%-nns" imprime uma string justificada à esquerda em um campo com nn caracteres de largura.
  • "%-*s", nn faz a mesma coisa - o * diz para tomar a largura do campo do próximo parâmetro.
  • Usando maxlength+2 para nn , nós temos dois espaços entre as colunas. Obviamente, o +2 pode ser ajustado.

O script acima funciona apenas para dois arquivos. Pode ser modificado trivialmente para lidar com três arquivos, ou para lidar com quatro arquivos, etc. mas isso seria tedioso e é deixado como um exercício. No entanto, não é difícil modificá-lo para lidar com qualquer número de arquivos:

awk '
        FNR==1  { file_num++ }
                { if (length > max_length[file_num]) max_length[file_num] = length
                  max_FNR[file_num] = FNR
                  save[file_num,FNR] = $0
                }
        END     { for (j=1; j<=file_num; j++) {
                        if (max_FNR[j] > global_max_FNR) global_max_FNR = max_FNR[j]
                  }
                  for (i=1; i<=global_max_FNR; i++) {
                        for (j=1; j<file_num; j++) printf "%-*s", max_length[j]+2, save[j,i]
                        print save[file_num,i]
                  }
                }
    '   file*

Isso é muito parecido com o meu primeiro script, exceto

  • Transforma max_length em uma matriz.
  • Transforma max_FNR em uma matriz.
  • Transforma save em uma matriz bidimensional.
  • Ele lê todos os arquivos, salvando todos o conteúdo. Em seguida, escreve todos a saída do bloco END .
por 24.10.2016 / 06:33