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 |||?


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.


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.


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


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.

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.
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.