Un mejor comando de pegar

11

Tengo los siguientes dos archivos (rellené las líneas con puntos para que cada línea de un archivo tenga el mismo ancho y puse file1 todo en mayúsculas para que quede más claro).

contents of file1:

ETIAM......
SED........
MAECENAS...
DONEC......
SUSPENDISSE

contents of file2

Lorem....
Proin....
Nunc.....
Quisque..
Aenean...
Nam......
Vivamus..
Curabitur
Nullam...

Observe que file2 es más largo que file1.

Cuando ejecuto este comando:

paste file1 file2

Me sale esta salida

ETIAM...... Lorem....
SED........ Proin....
MAECENAS... Nunc.....
DONEC...... Quisque..
SUSPENDISSE Aenean...
    Nam......
    Vivamus..
    Curabitur
    Nullam...

¿Qué puedo hacer para que la salida sea la siguiente?

ETIAM...... Lorem....
SED........ Proin....
MAECENAS... Nunc.....
DONEC...... Quisque..
SUSPENDISSE Aenean...
            Nam......
            Vivamus..
            Curabitur
            Nullam...

Lo intenté

paste file1 file2 | column -t

pero hace esto:

ETIAM......  Lorem....
SED........  Proin....
MAECENAS...  Nunc.....
DONEC......  Quisque..
SUSPENDISSE  Aenean...
Nam......
Vivamus..
Curabitur
Nullam...

no tan feo como el resultado original pero de todos modos incorrecto en columnas.

Tulains Córdova
fuente
2
pasteestá usando pestañas delante de las líneas del segundo archivo. Puede que tenga que usar un postprocesador para alinear las columnas de manera adecuada.
unxnut
3
paste file1 file2 | column -tn?
ninjalj
¿file1 siempre tiene columnas de tamaño fijo?
RSFalcon7
@ RSFalcon7 Sí, lo hace.
Tulains Córdova

Respuestas:

17

Suponiendo que no tiene caracteres de tabulación en sus archivos,

paste file1 file2 | expand -t 13

con el argumento -telegido adecuadamente para cubrir el ancho de línea máximo deseado en el archivo1.

OP ha agregado una solución más flexible:

Hice esto para que funcione sin el número mágico 13:

paste file1 file2 | expand -t $(( $(wc -L <file1) + 2 ))

No es fácil de escribir, pero se puede usar en un script.

Mark Plotnick
fuente
¡bonito! No sabía sobre expandir antes de leer tu respuesta :)
TabeaKischka
4

Pensé que awk podría hacerlo bien, así que busqué en Google "awk leyendo la entrada de dos archivos" y encontré un artículo sobre stackoverflow para usar como punto de partida.

Primero está la versión condensada, luego comentamos completamente debajo. Esto tardó más de unos minutos en resolverse. Me alegraría algunos refinamientos de personas más inteligentes.

awk '{if(length($0)>max)max=length($0)}
FNR==NR{s1[FNR]=$0;next}{s2[FNR]=$0}
END { format = "%-" max "s\t%-" max "s\n";
  numlines=(NR-FNR)>FNR?NR-FNR:FNR;
  for (i=1; i<=numlines; i++) { printf format, s1[i]?s1[i]:"", s2[i]?s2[i]:"" }
}' file1 file2

Y aquí está la versión completamente documentada de lo anterior.

# 2013-11-05 [email protected]
# Invoke thus:
#   awk -f this_file file1 file2
# The result is what you asked for and the columns will be
# determined by input file order.
#----------------------------------------------------------
# No matter which file we're reading,
# keep track of max line length for use
# in the printf format.
#
{ if ( length($0) > max ) max=length($0) }

# FNR is record number in current file
# NR is record number over all
# while they are equal, we're reading the first file
#   and we load the strings into array "s1"
#   and then go to the "next" line in the file we're reading.
FNR==NR { s1[FNR]=$0; next }

# and when they aren't, we're reading the
#   second file and we put the strings into
#   array s2
{s2[FNR]=$0}

# At the end, after all lines from both files have
# been read,
END {
  # use the max line length to create a printf format
  # the right widths
  format = "%-" max "s\t%-" max "s\n"
  # and figure the number of array elements we need
  # to cycle through in a for loop.
  numlines=(NR-FNR)>FNR?NR-FNR:FNR;
  for (i=1; i<=numlines; i++) {
     printf format, s1[i]?s1[i]:"", s2[i]?s2[i]:""
  }
}
Mike Diehn
fuente
1
+1 esta es la única respuesta que funciona con entradas arbitrarias (es decir, con líneas que pueden contener pestañas). No creo que esto pueda ser significativamente refinado / mejorado.
don_crissti
2

No es una muy buena solución, pero pude hacerlo usando

paste file1 file2 | sed 's/^TAB/&&/'

donde TAB se reemplaza con el carácter de tabulación.

unxnut
fuente
¿Cuál es el papel del &&comando sed?
coffeMug
1
Un solo &pone lo que se está buscando (una pestaña en este caso). Este comando simplemente reemplaza la pestaña al principio con dos pestañas.
unxnut
Tuve que cambiar TABpara \thacer que esto funcione en zsh en Ubuntu Debian. Y solo funciona si el archivo 1 tiene menos de 15 caracteres
rubo77
2

En Debian y derivados, columntiene una opción de -n nomerge que permite a la columna hacer lo correcto con campos vacíos. Internamente, columnutiliza la wcstok(wcs, delim, ptr)función, que divide una cadena de caracteres anchos en tokens delimitados por los caracteres anchos en el delimargumento.

wcstokcomienza saltando caracteres anchos delimantes de reconocer el token. La -nopción utiliza un algoritmo que no omite los caracteres anchos iniciales delim.

Desafortunadamente, esto no es muy portátil: -nes específico de Debian, y columnno está en POSIX, aparentemente es algo BSD.

ninjalj
fuente
2

Sacando los puntos que usaste para el relleno:

archivo1:

ETIAM
SED
MAECENAS
DONEC
SUSPENDISSE

file2:

Lorem
Proin
Nunc
Quisque
Aenean
Nam
Vivamus
Curabitur
Nullam

Prueba esto:

$ ( echo ".TS"; echo "l l."; paste file1 file2; echo ".TE" ) | tbl | nroff | more

Y obtendrás:

ETIAM         Lorem
SED           Proin
MAECENAS      Nunc
DONEC         Quisque
SUSPENDISSE   Aenean
              Nam
              Vivamus
              Curabitur
              Nullam
Jeff Taylor
fuente
Esto, al igual que las otras soluciones que usan paste, no imprimirá el resultado adecuado si hay líneas que contengan pestañas. +1 por ser diferente, sin embargo
don_crissti
+1. ¿Podría explicar cómo funciona la solución?
Tulains Córdova
1

Una awksolución que debería ser bastante portátil y debería funcionar para un número arbitrario de archivos de entrada:

# Invoke thus:
#   awk -F\\t -f this_file file1 file2

# every time we read a new file, FNR goes to 1

FNR==1 {
    curfile++                       # current file
}

# read all files and save all the info we'll need
{
    column[curfile,FNR]=$0          # save current line
    nlines[curfile]++               # number of lines in current file
    if (length > len[curfile])
            len[curfile] = length   # max line length in current file
}

# finally, show the lines from all files side by side, as a table
END {
    # iterate through lines until there are no more lines in any file
    for (line = 1; !end; line++) {
            $0 = _
            end = 1

            # iterate through all files, we cannot use
            #   for (file in nlines) because arrays are unordered
            for (file=1; file <= curfile; file++) {
                    # columnate corresponding line from each file
                    $0 = $0 sprintf("%*s" FS, len[file], column[file,line])
                    # at least some file had a corresponding line
                    if (nlines[file] >= line)
                            end = 0
            }

            # don't print a trailing empty line
            if (!end)
                    print
    }
}
ninjalj
fuente
¿Cómo se usa esto en el archivo 1 y el archivo 2? Llamé al script paste-awky lo intenté paste file1 file2|paste-awky lo intenté awk paste-awk file1 file2pero ninguno funcionó.
rubo77
Obtengoawk: Line:1: (FILENAME=file1 FNR=1) Fatal: Division by zero
rubo77
@ rubo77: awk -f paste-awk file1 file2debería funcionar, al menos para GNU awk y mawk.
ninjalj
Esto funciona, aunque es ligeramente diferente pasteya que hay menos espacio entre las dos filas. Y si el archivo de entrada no tiene todas las filas de la misma longitud, dará como resultado una fila de alineación a la derecha
rubo77
@ rubo77: el separador de campo se puede configurar con-F\\t
ninjalj