combinar arquivos de texto em colunas

52

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 colunas. Então eu tentei paste 1 2e sua saída é:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

No entanto, eu 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 manusear manualmente?


Adicionado:

Aqui está outro exemplo, em que o método Bruce quase o prende, exceto por um leve desalinhamento sobre o qual 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)
Tim
fonte
3
Esse último exemplo, com desalinhamento, é um delírio. Eu posso duplicar no Arch linux, pr (GNU coreutils) 8.12. Não consigo duplicá-lo em um Slackware antigo (11.0) que também tenho: pr (GNU coreutils) 5.97. O problema está no caractere '-' e ele está no pr, não no colar.
Bruce Ediger
11
Eu recebo a mesma coisa com o EM-DASH com ambos pre expand... columnsevita esse problema.
Peter.O
Eu produzi a saída para a maioria das respostas diferentes, exceto para awk + paste , que mudará para a esquerda as colunas mais à direita se um arquivo esquerdo for menor do que qualquer t à direita dele. O mesmo e mais se aplicam a 'colar + coluna', que também tem esse problema com linhas em branco na (s) coluna (s) esquerda (s) ... Se você deseja ver todas as saídas juntas. aqui está o link: paste.ubuntu.com/643692 Eu usei 4 colunas.
Peter.O
Acabei de perceber algo enganoso no link paste.ubuntu ... originalmente configurei os dados para testar meus scripts (e isso levou a fazer os outros) ... então os campos que dizem ➀ unicode may render oddly but the column count is ok definitivamente não se aplicam wc-paste-pre wc-paste-preles mostram diferenças na contagem de colunas. Os outros estão ok.
Peter.O
11
@BruceEdiger: O problema de alinhamento ocorre quando caracteres não ASCII são usados ​​(em sua pergunta, o OP usou um traço (-) em vez de um caractere de menos (-)), provavelmente devido a um manuseio incorreto ou não prdo multibyte caracteres no código do idioma atual (geralmente UTF8).
WhiteWinterWolf

Respostas:

68

Você só precisa do columncomando e diz para ele usar guias para separar colunas

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

Para resolver a controvérsia da "célula vazia", ​​precisamos apenas da -nopção de 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 de manual da minha coluna indica que -né uma "extensão Debian GNU / Linux". Meu sistema Fedora não exibe o problema de células vazias: parece derivado do BSD e a página do manual diz "A versão 2.23 mudou a opção -s para não ser gananciosa"

Glenn Jackman
fonte
4
Glenn: Você é o herói da hora! Eu sabia que havia algo assim por perto, mas não conseguia me lembrar. Eu tenho espreitado nesta questão; esperando por você :) ... é columnclaro; quão óbvio (em retrospecto) +1 ... Obrigado ...
Peter.O
4
Acabei de notar que column -s $'\t' -tignora células vazias , resultando em todas as células subsequentes à direita (nessa linha) para mover para a esquerda; ou seja, como resultado de uma linha em branco em um arquivo, ou sendo mais curto ... :(
Peter.O
11
@masi, corrigido
glenn jackman
-n não funciona no RHEL. Existe uma alternativa?
Koshur
Finalmente, posso comentar, por isso, quero observar que adicionei anteriormente uma resposta abaixo que aborda o problema de Peter.O com execuções de células vazias usando valores nulos.
techno
11

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

paste file1 file2 | pr -t -e24

O "-e24" é "a tab de expansão para 24 espaços". Felizmente, pastecoloca um caractere de tabulação entre as colunas, para prexpandi-lo. Eu escolhi 24 contando os caracteres em "Recursivamente enumerável" e adicionando 2.

Bruce Ediger
fonte
Obrigado! O que significa "tab tab expandido para 24 espaços" significa?
Tim
Também atualizo com um exemplo em que seu método quase o prende, exceto por um leve desalinhamento.
Tim
Tradicionalmente, "tabstops" atingem a cada 8 espaços. "123TABabc" seria impresso com o caractere 'a' com 8 larguras de caracteres desde o início da linha. Configurá-lo para 24 colocaria o 'a' em 24 larguras de caracteres desde o início da linha.
Bruce Ediger
Você diz que "-e24" é "a tab de expansão para 24 espaços" , então por que não usar o expandcomando diretamente paste file1 file2 | expand -t 24:?
WhiteWinterWolf
11
@Masi - minha resposta é semelhante, mas menos complicada que a resposta do @ techno abaixo. Não invoca, sedentão há um processo que não é executado. Ele usa prum comando antigo, datado dos dias Unix SysV, eu acho, para que possa existir em mais instalações que expand. É apenas a velha escola, em suma.
11556 Bruce Ediger #
9

Atualização : 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 paste... Ele é usado htmlpara criar o quadro, para que seja ajustado. 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\1\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 olhei bem de perto para eles; aqui está o que eu encontrei:

paste# Esta ferramenta é comum a todas as respostas apresentadas até o momento # Ela pode lidar com vários arquivos; portanto, várias colunas ... Bom! # Delimita cada coluna com uma guia ... Bom. # Sua saída não é tabulada.

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

column # Ele remove o delimitador de tabulação, para que a identificação do campo seja puramente feita por colunas que parecem funcionar muito bem .. Não vi nada errado ... # Além de não ter um delimitador exclusivo, ele funciona bem!

expand # Apenas possui uma configuração de guia única, portanto é imprevisível além de 2 colunas # O alinhamento das colunas não é preciso ao manipular unicode e remove o delimitador de tabulação, para que a identificação do campo seja puramente pelo alinhamento da coluna

pr# Apenas possui uma configuração de guia única, portanto é imprevisível além de 2 colunas. # O alinhamento das colunas não é preciso ao manipular o unicode e remove o delimitador de tabulação, portanto a identificação do campo é puramente pelo alinhamento da coluna

Para mim, columné a melhor solução óbvia como uma linha. Você deseja que o delimitador ou uma tablatura de seus arquivos, conforme a arte ASCII, continue lendo, caso contrário ... columnsé muito bom :)


Aqui está um script que pega qualquer número de arquivos e cria uma apresentação tabulada da arte ASCII. (Lembre-se de que o unicode pode não renderizar na largura esperada, por exemplo, ௵ que é um único caractere. Isso é bem diferente da coluna os números estão 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á a minha resposta original (aparada um pouco no lugar do script acima)

Usando wcpara obter a largura da coluna e sedpara o teclado direito com um caractere visível. (apenas para este exemplo) ... e depois pastejuntar as duas colunas com um caractere Tab ...

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ê deseja 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...........
Peter.O
fonte
Obrigado! Você trabalhou bastante. Isso é incrível.
Tim
5

Você está quase lá. pastecoloca um caractere de tabulação entre cada coluna; portanto, tudo que você precisa fazer é expandir as tabs. (Presumo que seus arquivos não contenham guias.) Você precisa determinar a largura da coluna esquerda. Com os utilitários GNU (recentes o suficiente), wc -Lmostra o comprimento da linha mais longa. Em outros sistemas, faça uma primeira passagem com awk. A +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ê possui o utilitário de coluna BSD, pode usá-lo para determinar a largura da coluna e expandir as guias de uma só vez. ( é um caractere de tabulação literal; em bash / ksh / zsh você pode usar em $'\t'vez disso, e em qualquer shell que pode usar "$(printf '\t')".)

paste left.txt right.txt | column -s '␉' -t
Gilles 'SO- parar de ser mau'
fonte
Na minha versão de wc, o comando precisa ser: wc -L <left.txt... porque, quando um nome de arquivo é especificado como uma linha de comando arg , seu nome é enviado para stdout
Peter
4

Isso é de várias etapas, portanto 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 awk para preencher file1.txt, preenchendo cada linha com menos de 22 caracteres até 22 com a printfinstrução

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

Nota: Para o FS, use uma sequência que não exista file1.txt.

3) Use a pasta 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.

bahamat
fonte
No seu código para encontrar a linha mais longa, você precisa while IFS= read -r line; caso contrário, o shell irá alterar os espaços em branco e as barras invertidas. Mas o shell não é a melhor ferramenta para esse trabalho; versões recentes do coreutils GNU ter wc -L(ver resposta de Fred), ou você pode usar awk: awk 'n<length {n=length} END {print +n}'.
Gilles 'SO- stop be evil' ''
4

Não consigo comentar a resposta de glenn jackman, por isso estou adicionando isso para resolver o problema de células vazias que Peter.O observou. A adição de um caractere nulo antes de cada guia elimina as execuções de delimitadores que são tratados como uma única quebra e solucionam o problema. (Eu originalmente usei espaços, mas usar o caractere nulo elimina o espaço extra entre as colunas.)

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

Se o caractere nulo causar problemas por vários motivos, tente:

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

ou

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

Ambas sede columnparecem variar na implementação entre os tipos e versões do Unix / Linux, especialmente BSD (e Mac OS X) vs. GNU / Linux.

techno
fonte
Esse comando sed parece não fazer nada. Substituo o comando column por od -ce não vejo bytes nulos. Isso é no centos e no ubuntu.
Glenn Jackman
11
Isso funcionou para mim no RedHat EL4. O sed e a coluna parecem variar ao longo do tempo e do sistema. No Ubuntu 14.4, o uso \0não funcionou como um nullno sed, mas \x0funcionou. No entanto, a coluna deu um line too longerro. A coisa mais simples parece ser usar um espaço e conviver com o personagem extra.
techno
0

Com base na resposta de bahamat : isso pode ser feito inteiramente awk, lendo os arquivos apenas uma vez e não criando nenhum arquivo temporário. Para resolver o problema como indicado, 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

Como em muitos awkscripts desse tipo, o acima é lido primeiro file1, salvando todos os dados da savematriz e computando simultaneamente o comprimento máximo da linha. Em seguida, ele lê file2 e imprime os file1dados salvos ( ) lado a lado com os file2dados atuais ( ). Por fim, se file1for maior que file2(tem mais linhas), imprimimos as últimas linhas de file1 (aquelas para as quais não há linha correspondente na segunda coluna).

Em relação ao printfformato:

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

O script acima funciona apenas para dois arquivos. Ele pode ser modificado trivialmente para manipular três arquivos ou para 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 semelhante ao meu primeiro script, exceto

  • Ele se transforma max_lengthem uma matriz.
  • Ele se transforma max_FNRem uma matriz.
  • Ele se transforma saveem uma matriz bidimensional.
  • Ele lê todos os arquivos, salvando todo o conteúdo. Em seguida, ele grava toda a saída do ENDbloco.
G-Man Diz 'Reinstate Monica'
fonte
Eu sei que essa pergunta é antiga; Eu apenas tropecei nisso. Concordo que pasteé a melhor solução; especificamente, de Glenn Jackman paste file1 file2 | column -s $'\t' -t. Mas pensei que seria divertido tentar melhorar a awkabordagem.
G-Man Diz 'Reinstate Monica'