Combine dos archivos línea por línea con el símbolo delimitador de triple tubería "|||"

14

Tengo dos archivos paralelos con el mismo número de líneas en dos idiomas y planeo fusionar estos dos archivos línea por línea con el delimitador |||. Por ejemplo, los dos archivos son los siguientes:

Presentar un:

1Mo 1,1 I love you.
1Mo 1,2 I like you.
Hi 1,3 I am hungry.
Hi 1,4 I am foolish.

Archivo B:

1Mo 1,1 Ich liebe dich.
1Mo 1,2 Ich mag dich.
Hi 1,3 Ich habe Durst.
Hi 1,4 Ich bin neu.

El resultado esperado es así:

1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. ||| Hi 1,4 Ich bin neu.

Probé el pastecomando como:

paste -d "|||" fileA fileB

Pero la salida devuelta solo contiene una tubería como:

1Mo 1,1 I love you. |1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. |1Mo 1,2 Ich mag dich.

¿Hay alguna manera de separar cada par de líneas por tubería tripa |||?

Fruncir el ceño
fuente
8
paste -d '|||' fileA - - fileB < /dev/null
Stéphane Chazelas
55
fuera del tema, pero sus traducciones no son correctas;) "Ich habe Durst" = Soy cariñoso, "Ich bin neu" = Soy nuevo ... no significa necesariamente que sea tonto. ... por si acaso estás aprendiendo alemán ...
dave_alcarin
@ StéphaneChazelas Thx, pero mi salida todavía solo contiene una tubería ...
Fruncir el ceño el
@dave_alcarin Dank sehr!
Fruncir el ceño el

Respuestas:

20

Con pasta POSIX :

:|paste -d ' ||| ' fileA - - - - fileB

pasteconcatenará las líneas correspondientes de todos los archivos de entrada. Aquí tenemos seis archivos, fileAcuatro archivos ficticios de entrada estándar -y fileB.

La lista de delimitadores incluye un espacio, tres tubos y un espacio en ese orden se utilizará pastecircularmente.

Para la primera línea de seis archivos, fileAse concatenará con el primer archivo ficticio (que no es nada, gracias al operador no-op :), produce line1-fileA<space>.

El primer archivo ficticio se concatenará con el segundo por una tubería, producir line1-fileA |, luego el segundo archivo ficticio con el tercer archivo ficticio, producir line1-fileA ||, el tercer archivo ficticio con el cuarto archivo ficticio, producir line1-fileA |||.

Y el cuarto archivo ficticio con fileB, produce line1-fileA ||| line1-fileB.

Esos pasos se repetirán para todas las líneas y le darán el resultado esperado.


El uso de :|es para escribir menos, y se usa principalmente en shell interactivo. En un script, debe usar:

</dev/null paste -d ' ||| ' fileA - - - - fileB

para evitar que se genere una subshell.

Cuonglm
fuente
1
+1 para el :|. alternativa inteligente a</dev/null
cas
44
... y +1 para el uso inteligente de 4 archivos ficticios de entrada estándar con - - - -, pero la próxima vez incluso puede escribir un par de líneas para explicación :)
Hastur
Gracias, pero todavía obtengo la salida con una pipa ...
Fruncir el ceño el
@hui, ¿ejecutó el comando exactamente como se indica, incluidos todos los guiones y caracteres de espacio? ¿Cuál es tu sistema operativo?
Stéphane Chazelas
:|paste -d '|' fileA - - fileBda la versión más correcta sin el delimitador de espacio.
Pål GD
7

Bueno, esto no usa sed, awk o grep, pero puedes hacerlo con bastante facilidad en bash. El comando es:

(while IFS= read -r a <&3 && IFS= read -r b <&4; do echo "$a ||| $b"; done) 3<fileA 4<fileB

El problema con pegar es que el delimitador es un solo carácter. También podría insertar un solo carácter y utilizarlo para transformarlo, pero sería propenso a errores si el carácter ya apareciera en el archivo de entrada.

usuario3188445
fuente
2
Su solución no funcionará si la línea contiene caracteres de barra invertida o si comienza con un guión. Desea usar IFS=antes de cada uno read. Puedes hacerlo fácilmente con paste. Vea mi respuesta , y también esta para ver por qué debería evitar usar el whilebucle en el script de shell.
Cuonglm
Funciona para mi archivo. ¡Muchas gracias!
Fruncir el ceño el
5

Una versión awk (GNU)

awk '{printf ("%s ||| ", $0); getline < "fileB"; print $0 }' fileA

Con el getlinecomando en awk, puede establecer $0(todas las variables para columnas) desde el siguiente registro de entrada, si getline < "filename"establece el siguiente $0desde el archivo especificado.

getline <"archivo" Establece $ 0 desde el siguiente registro del archivo; establecer NF.


¿Por qué su intento no funcionó como esperaba? De man pastepodemos leer

-d, --delimiters=LIST
     reuse characters from LIST instead of TABs

pero usa los delimitadores uno para cada columna .

Entonces el comando
paste -d '|*|*' fileA fileB fileA fileBme da líneas como

Hi 1,3 I am hungry.|Hi 1,3 Ich habe Durst.*Hi 1,3 I am hungry.|Hi 1,3 Ich...
Hi 1,4 I am foolish.|Hi 1,4 Ich bin neu.*Hi 1,4 I am foolish.|Hi 1,4 Ich...


Una sedsolución que sugiero evitar incluso si está cerca de su intento original, ya que adapta el comportamiento obtenido a su propósito original:

 paste -d '|' fileA fileB | sed 's/|/|||/g'

Para evitarlo porque sustituye cada patrón |con el nuevo |||, pero debe asumir que el símbolo de tubería ( |) no está presente en sus datos ; de lo contrario, debe tratar casos especiales y hacer que el código sea más complejo para evitar efectos secundarios.


Una variante con la cadena Here [ 1 construcción ]<<<

 paste -d ' ||| ' fileA - - - - fileB  <<< ''

Establece 5 delimitadores con -d ' ||| '(espacio, |, |, |, espacio) y 4 archivos ficticios ( - - - -) que tomarán datos de la cadena vacía'' .


Probado en GNU Awk 4.0.1, pegar (GNU coreutils) 8.21 y sed (GNU sed) 4.2.2

Hastur
fuente
Gracias, ¡el comando awk funciona!
Fruncir el ceño el
1
De nada. Se actualizó la respuesta agregando un sedejemplo para evitar (:-)) y más comentarios.
Hastur
4

Si desea evitar la magia y el drama de los delimitadores circulares y los archivos ficticios, puede agregar su delimitador a un archivo antes de pegarlos:

paste <(sed 's/$/ |||/' filea) fileb

da

1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. |||    Hi 1,4 Ich bin neu.
enésimo
fuente
Me gusta esto por simplicidad. Sin embargo, creo que quieres decir "anteponer", no "agregar". Mira la respuesta awk de Hastur para la versión awk de esto.
Comodín el
Debe cambiar la sustitución del proceso a una tubería, de modo que no tenga el límite para el número de proyectiles que lo admiten.
Cuonglm
@Wildcard sí, anteponer, pero lo reescribiré para agregar a filea. Creo que awk es un poco exagerado para esto.
2015
@cuonglm cierto, pero quería evitar las tuberías para mayor claridad. Sentí que una tubería haría que empezara a parecerse a los archivos ficticios, pero tienes razón
2015
0

puedes hacerlo también en python de esta manera.

lines1 = [ line.rstrip() for line in open("file1") ]
lines2 = [ line.rstrip() for line in open("file2") ]
for i in xrange((len(lines1))): print lines1[i] + " ||| " + lines2[i]
... 
1Mo 1,1 I love you. ||| 1Mo 1,1 Ich liebe dich.
1Mo 1,2 I like you. ||| 1Mo 1,2 Ich mag dich.
Hi 1,3 I am hungry. ||| Hi 1,3 Ich habe Durst.
Hi 1,4 I am foolish. ||| Hi 1,4 Ich bin neu.
c4f4t0r
fuente