combinar archivos de texto en columnas

52

Tengo dos archivos de texto. El primero tiene contenido:

Languages
Recursively enumerable
Regular

mientras que el segundo tiene contenido:

Minimal automaton
Turing machine
Finite

Quiero combinarlos en un archivo en columna. Así que lo intenté paste 1 2y su salida es:

Languages   Minimal automaton
Recursively enumerable  Turing machine
Regular Finite

Sin embargo, me gustaría alinear bien las columnas, como

Languages               Minimal automaton
Recursively enumerable  Turing machine
Regular                 Finite

Me preguntaba si sería posible lograr eso sin un manejo manual.


Adicional:

Aquí hay otro ejemplo, donde el método de Bruce casi lo clava, excepto una ligera desalineación sobre la cual me pregunto 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
fuente
3
Ese último ejemplo, con desalineación, es un desastre. Puedo duplicarlo en Arch Linux, pr (GNU coreutils) 8.12. No puedo duplicarlo en un viejo Slackware (11.0) que también tengo alrededor: pr (GNU coreutils) 5.97. El problema es con el carácter '-', y está en pr, no en pegar.
Bruce Ediger
1
Me pasa lo mismo con el EM-DASH con ambos pry expand... columnsevita este problema.
Peter
He producido resultados para la mayoría de las diferentes respuestas, excepto awk + paste , que desplazará a la izquierda la (s) columna (s) más a la derecha si un archivo izquierdo es más corto que cualquiera a la derecha. Lo mismo, y más, se aplica a 'pegar + columna' que también tiene este problema con las líneas en blanco en la (s) columna (s) izquierda (s) ... Si desea ver todas las salidas juntas. aquí está el enlace: paste.ubuntu.com/643692 He usado 4 columnas.
Peter
Me he dado cuenta de algo engañoso en el paste.ubuntu enlace ... Me programado originalmente los datos para probar mis guiones, (y que llevó a hacer los otros) ... por lo que los campos que dicen ➀ unicode may render oddly but the column count is ok definitivamente hace que no se aplican a wc-paste-pry wc-paste-prEllos muestra diferencias en el recuento de columnas.
Peter.O
1
@BruceEdiger: El problema de alineación ocurre cuando se usan caracteres que no son ASCII (en su pregunta, el OP usó un guión (-) en lugar de un carácter menos (-)), probablemente debido a un mal o ningún manejo por parte prdel multibyte caracteres en el entorno local actual (generalmente UTF8).
WhiteWinterWolf

Respuestas:

68

Solo necesita el columncomando y dígale que use pestañas para separar columnas

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

Para abordar la controversia de la "celda vacía", solo necesitamos la -nopción 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

La página de manual de mi columna indica que -nes una "extensión Debian GNU / Linux". Mi sistema Fedora no presenta el problema de celda vacía: parece derivarse de BSD y la página del manual dice "La versión 2.23 cambió la opción -s para que no sea codiciosa"

Glenn Jackman
fuente
44
Glenn: ¡Eres el héroe de la hora! Sabía que había algo como esto, pero no podía recordarlo. He estado al acecho en esta pregunta; esperando a que :) ... column, por supuesto; lo obvio (en retrospectiva) 1 ... Gracias ...
Peter.O
44
Acabo de notar que column -s $'\t' -tignora las celdas vacías , lo que da como resultado que todas las celdas posteriores a la derecha (en esa línea) se muevan a la izquierda; es decir, como resultado de una línea en blanco en un archivo, o que sea más corta ... :(
Peter.O
1
@masi, corregido
glenn jackman
-n no funciona en RHEL. ¿Hay una alternativa?
Koshur
Finalmente puedo comentar, así que quiero tener en cuenta que anteriormente agregué una respuesta a continuación que aborda el problema de Peter.O con corridas de celdas vacías usando nulos.
techno
11

Estás buscando el práctico prcomando dandy :

paste file1 file2 | pr -t -e24

El "-e24" es "expandir tabulaciones a 24 espacios". Afortunadamente, pastecoloca un carácter de tabulación entre columnas, por lo que prpuede expandirlo. Elegí 24 contando los caracteres en "recursivamente enumerable" y agregando 2.

Bruce Ediger
fuente
¡Gracias! ¿Qué significa "expandir tabulaciones a 24 espacios"?
Tim
También actualizo con un ejemplo donde su método casi lo clava, excepto una ligera desalineación.
Tim
Tradicionalmente, "tabstops" golpean cada 8 espacios. "123TABabc" se imprime con el carácter 'a' de 8 caracteres de ancho desde el comienzo de la línea. Establecerlo en 24 pondría la 'a' en 24 anchos de caracteres desde el comienzo de la línea.
Bruce Ediger
Usted dice que "-e24" es "expandir tabulaciones a 24 espacios" , entonces ¿por qué no usar el expandcomando directamente paste file1 file2 | expand -t 24:?
WhiteWinterWolf
1
@Masi: mi respuesta es similar pero menos complicada que la respuesta de @ techno a continuación. No invoca, sedpor lo que hay un proceso que no se ejecuta. Utiliza prcuál es un comando antiguo, que data de los días de Unix SysV, creo, por lo que podría existir en más instalaciones que expand. Es solo la vieja escuela, en resumen.
Bruce Ediger el
9

Actualización : Aquí hay un script mucho más simple (el que está al final de la pregunta) para la salida tabulada. Simplemente pásale el nombre de archivo como lo harías paste... Se usa htmlpara hacer el marco, por lo que es modificable. Conserva múltiples espacios, y la alineación de la columna se conserva cuando encuentra caracteres unicode. Sin embargo, la forma en que el editor o el espectador representa el Unicode es otra cuestión completamente ...

┌──────────────────────┬────────────────┬──────────┬────────────────────────────┐
│ 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'

---

Una sinopsis de las herramientas presentadas en las respuestas (hasta ahora).
Los he mirado de cerca; Esto es lo que he encontrado:

paste# Esta herramienta es común a todas las respuestas presentadas hasta ahora # Puede manejar múltiples archivos; por lo tanto múltiples columnas ... ¡Bien! # Delimita cada columna con una pestaña ... Bien. # Su salida no está tabulada.

¡Todas las herramientas a continuación eliminan este delimitador! ... Malo si necesita un delimitador.

column # Elimina el delimitador de tabulación, por lo que la identificación del campo es puramente por columnas que parece manejar bastante bien ... No he visto nada extraño ... # Además de no tener un delimitador único, ¡funciona bien!

expand # Solo tiene una configuración de pestaña única, por lo que es impredecible más allá de 2 columnas # La alineación de las columnas no es precisa cuando se maneja unicode, y elimina el delimitador de pestaña, por lo que la identificación del campo es puramente por alineación de columna

pr# Solo tiene una configuración de pestaña única, por lo que es impredecible más allá de 2 columnas. # La alineación de las columnas no es precisa cuando se maneja unicode, y elimina el delimitador de Tabulación, por lo que la identificación del campo es puramente por la alineación de la columna.

Para mí, columnes la mejor solución obvia como una línea. Si quieres el delimitador o una tabulación de arte ASCII de tus archivos, sigue leyendo, de lo contrario ... columnses bastante bueno:) ...


Aquí hay una secuencia de comandos que toma cualquier cantidad de archivos y crea una presentación tabulada de arte ASCII. los números están equivocados, como es el caso en algunas de las utilidades mencionadas anteriormente.) ... La salida del script, que se muestra a continuación, es de 4 archivos de entrada, llamados 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

Aquí está mi respuesta original (recortada un poco en lugar del guión anterior)

Utilizando wcpara obtener el ancho de la columna, y sedpara el pad derecho con un carácter visible. (solo para este ejemplo) ... y luego pastepara unir las dos columnas con un carácter 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

Si desea rellenar la columna derecha:

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
fuente
¡Gracias! Has hecho bastante trabajo. Eso es increíble.
Tim
5

Ya casi estás ahí. pastecoloca un carácter de tabulación entre cada columna, por lo que todo lo que necesita hacer es expandir las pestañas. (Supongo que sus archivos no contienen pestañas). Es necesario determinar el ancho de la columna izquierda. Con las utilidades GNU (lo suficientemente recientes), wc -Lmuestra la longitud de la línea más larga. En otros sistemas, haga un primer pase con awk. El +1es la cantidad de espacio en blanco que desea entre columnas.

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}')

Si tiene la utilidad de columna BSD, puede usarla para determinar el ancho de la columna y expandir las pestañas de una vez. ( es un carácter de tabulación literal; en bash / ksh / zsh puede usar $'\t'en su lugar, y en cualquier shell que pueda usar "$(printf '\t')").

paste left.txt right.txt | column -s '␉' -t
Gilles 'SO- deja de ser malvado'
fuente
En mi versión de wc, el comando debe ser: wc -L <left.txt... porque, cuando un nombre de archivo se spedified como una línea de comando arg , su nombre se emite por la salida estándar
Peter.O
4

Esto es de varios pasos, por lo que no es óptimo, pero aquí va.

1) Encuentra la longitud de la línea más larga file1.txt.

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

Con su ejemplo, la línea más larga es 22.

2) Use awk para rellenar file1.txt, rellenando cada línea con menos de 22 caracteres hasta 22 con la printfinstrucción.

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

Nota: Para FS, use una cadena que no exista en file1.txt.

3) Use pegar como lo hizo antes.

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

Si esto es algo que haces a menudo, esto se puede convertir fácilmente en un script.

bahamat
fuente
En su código para encontrar la línea más larga, necesita while IFS= read -r line, de lo contrario, el shell alterará los espacios en blanco y las barras invertidas. Pero el shell no es la mejor herramienta para ese trabajo; Las versiones recientes de GNU coreutils han wc -L(véase la respuesta de Fred), o se puede usar awk: awk 'n<length {n=length} END {print +n}'.
Gilles 'SO- deja de ser malvado'
4

No puedo comentar sobre la respuesta de Glenn Jackman, así que agrego esto para abordar el problema de las celdas vacías que Peter.O notó. Agregar un carácter nulo antes de cada pestaña elimina las corridas de delimitadores que se tratan como un salto único y soluciona el problema. (Originalmente usé espacios, pero usar el carácter nulo elimina el espacio adicional entre columnas).

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

Si el carácter nulo causa problemas por varias razones, intente:

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

o

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

Ambas, sedy columnparecen variar en la implementación entre sabores y versiones de Unix / Linux, especialmente BSD (y Mac OS X) frente a GNU / Linux.

techno
fuente
Ese comando sed parece no hacer nada. Reemplazo el comando de columna con od -cy no veo ningún byte nulo. Esto está en centos y ubuntu.
Glenn Jackman
1
Esto funcionó para mí en RedHat EL4. Tanto sed como columna parecen variar con el tiempo y el sistema. En Ubuntu 14.4 el uso \0no funcionaba como un nullin sed, pero lo \x0hizo. Sin embargo, entonces la columna dio un line too longerror. Lo más simple parece ser usar un espacio y vivir con el personaje extra.
techno
0

Basándose en la respuesta de bahamat : esto se puede hacer completamente awk, leyendo los archivos solo una vez y sin crear ningún archivo temporal. Para resolver el problema como se indica, haga

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

Al igual que con muchos awkscripts de este tipo, lo primero que se lee arriba es file1guardar todos los datos en la savematriz y calcular simultáneamente la longitud máxima de la línea. Luego lee file2 e imprime los file1datos guardados ( ) junto con los file2datos actuales ( ). Finalmente, si file1es más largo que file2(tiene más líneas), imprimimos las últimas líneas de file1 (las que no tienen una línea correspondiente en la segunda columna).

En cuanto al printfformato:

  • "%-nns"imprime una cadena justificada a la izquierda en un campo de nncaracteres de ancho.
  • "%-*s", nnhace lo mismo: *le dice que tome el ancho del campo del siguiente parámetro.
  • Al usar for , obtenemos dos espacios entre las columnas. Obviamente se puede ajustar.maxlength+2nn+2

El script anterior funciona solo para dos archivos. Se puede modificar trivialmente para manejar tres archivos, o para manejar cuatro archivos, etc., pero esto sería tedioso y se deja como un ejercicio. Sin embargo, resulta que no es difícil modificarlo para manejar cualquier número de archivos:

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*

Esto es muy similar a mi primer script, excepto

  • Se convierte max_lengthen una matriz.
  • Se convierte max_FNRen una matriz.
  • Se convierte saveen una matriz bidimensional.
  • Lee todos los archivos y guarda todos los contenidos. Luego escribe toda la salida del ENDbloque.
G-Man dice 'restablecer a Mónica'
fuente
Sé que esta pregunta es vieja; Me topé con eso. Estoy de acuerdo en que pastees la mejor solución; específicamente, Glenn Jackman paste file1 file2 | column -s $'\t' -t. Pero pensé que sería divertido intentar mejorar el awkenfoque.
G-Man dice 'reinstalar a Monica' el