¿Cómo fusionar cada dos líneas en una desde la línea de comando?


151

Tengo un archivo de texto con el siguiente formato. La primera línea es la "CLAVE" y la segunda línea es el "VALOR".

KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1

Necesito el valor en la misma línea que la clave. Entonces la salida debería verse así ...

KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

Sería mejor si pudiera usar algún delimitador como $o ,:

KEY 4048:1736 string , 3

¿Cómo fusiono dos líneas en una?


¡Hay muchas maneras de hacer esto! He hecho un pequeño banco con pr, paste, awk, xargs, sedypure bash ! ( xargses el más lento, más lento que bash !)
F. Hauri

Respuestas:


182

awk:

awk 'NR%2{printf "%s ",$0;next;}1' yourFile

tenga en cuenta que hay una línea vacía al final de la salida.

sed:

sed 'N;s/\n/ /' yourFile

No funciona con salida de color. Intenté todo en estas preguntas y respuestas y nada funcionó cuando la salida es de color ansi. Probado en Ubuntu 13.04
Leo Gallucci

1
@elgalu: Porque los colores ANSI son solo un montón de combinaciones de caracteres de escape. Haga un hexadecimal en tal salida, para ver lo que tiene.
not2qubit

77
Esta solución awk puede romperse si se encuentran printfcadenas de expansión como %sdentro $0. Ese fracaso puede evitarse así:'NR%2{printf "%s ",$0;next;}1'
ghoti

9
Debido a que es realmente difícil buscar en Google, ¿qué significa 1después de la llave de cierre?
erikbwork


243

paste es bueno para este trabajo:

paste -d " "  - - < filename

10
Creo que esta es la mejor solución presentada, a pesar de no usar ni sed ni awk. En la entrada que es un número impar de líneas, la solución awk de Kent omite la nueva línea final, su solución sed omite la línea final en su entrada y mi solución repite la última línea. paste, por otro lado, se comporta perfectamente. +1.
ghoti

8
A menudo uso cutpero siempre me olvido paste. Es genial para este problema. Necesitaba combinar todas las líneas de stdin y lo hice fácilmente paste -sd ' ' -.
Clint Pachl

44
Simple y hermoso!
krlmlr

8
tan -malo stdin, tan paste - -malo leer de stdin, luego leer de stdin, puedes apilar tantos como quieras, espero.
ThorSummoner

1
Sí, @ThorSummoner ... Tuve que pegar cada tres líneas en una sola línea y pegué - - - y funcionó perfectamente.
Daniel Goldfarb

35

Alternativa a sed, awk, grep:

xargs -n2 -d'\n'

Esto es mejor cuando desea unir N líneas y solo necesita una salida delimitada por espacios.

Mi respuesta original fue xargs -n2que se separa en palabras en lugar de líneas. -dse puede usar para dividir la entrada por cualquier carácter individual.


44
Este es un buen método, pero funciona con palabras, no con líneas. Para que funcione en líneas, podría agregar-d '\n'
Don Hatch,

2
Wow, soy un xargsusuario habitual pero no lo sabía. Gran consejo
Sridhar Sarnobat

1
Me encanta esto. Muy limpio.
Alexander Guo

28

Hay más formas de matar a un perro que colgando. [1]

awk '{key=$0; getline; print key ", " $0;}'

Ponga el delimitador que desee dentro de las comillas.


Referencias

  1. Originalmente, "Un montón de formas de desollar al gato", volvió a una expresión más antigua y potencialmente originaria que tampoco tiene nada que ver con las mascotas.

Amo esta solución
luis.espinal

55
Como dueño de un gato, no aprecio este tipo de humor.
witkacy26

44
@ witkacy26, expresión ajustada según su preocupación.
ghoti

Me encanta esta solución awk pero no entiendo cómo funciona: S
Rubendob

@Rubendob - awk lee cada línea de entrada y la coloca en la variable $0. El getlinecomando también toma "la siguiente" línea de entrada y la coloca $0. Entonces, la primera instrucción toma la primera línea, y el comando de impresión concatena lo que se guardó en la variable keycon una cadena que contiene una coma, junto con la línea que se obtuvo usando getline. Más claro? :)
ghoti

12

Aquí está mi solución en bash:

while read line1; do read line2; echo "$line1, $line2"; done < data.txt

11

Aunque parece que las soluciones anteriores funcionarían, si ocurre una sola anomalía en el documento, la salida se partiría en pedazos. A continuación se muestra un poco más seguro.

sed -n '/KEY/{
N
s/\n/ /p
}' somefile.txt

3
¿Por qué es más seguro? ¿Qué /KEY/hacer? ¿Qué hace el pal final?
Stewart

las /KEY/búsquedas de la línea con el KEY. el pimprime el resultado. es más seguro porque solo aplica la operación en líneas con un KEY.
minghua

11

Aquí hay otra forma con awk:

awk 'ORS=NR%2?FS:RS' file

$ cat file
KEY 4048:1736 string
3
KEY 0:1772 string
1
KEY 4192:1349 string
1
KEY 7329:2407 string
2
KEY 0:1774 string
1

$ awk 'ORS=NR%2?FS:RS' file
KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

Como lo indicó Ed Morton en los comentarios, es mejor agregar llaves para mayor seguridad y parens para portabilidad.

awk '{ ORS = (NR%2 ? FS : RS) } 1' file

ORSsignifica Separador de registro de salida. Lo que estamos haciendo aquí es probar una condición usando la NRque almacena el número de línea. Si el módulo de NRes un valor verdadero (> 0), establecemos el Separador de campo de salida en el valor de FS(Separador de campo) que, de forma predeterminada, es espacio; de lo contrario, asignamos el valor de RS(Separador de registro) que es nueva línea.

Si desea agregar ,como separador, utilice lo siguiente:

awk '{ ORS = (NR%2 ? "," : RS) } 1' file

1
Definitivamente el enfoque correcto para +1, pero me pregunto cuál es la condición que se está evaluando para invocar la acción predeterminada de imprimir el registro. ¿Es que la asignación tuvo éxito? ¿Es simple ORSy se trata como si trueORS obtiene un valor que no es cero o una cadena nula y se despierta adivinando correctamente que debería ser una picadura en lugar de una comparación numérica? ¿Es algo más? Realmente no estoy seguro, así que lo habría escrito como awk '{ORS=(NR%2?FS:RS)}1' file. Puse entre paréntesis la expresión ternaria para garantizar la portabilidad también.
Ed Morton

1
@EdMorton Sí, acabo de ver un par de votos a favor sobre esta respuesta que estaba a punto de actualizar para incluir los frenos por seguridad. Agregará parens también.
jaypal singh

7

"ex" es un editor de línea programable que pertenece a la misma familia que sed, awk, grep, etc. Creo que podría ser lo que estás buscando. Muchos clones / sucesores vi modernos también tienen un modo vi.

 ex -c "%g/KEY/j" -c "wq" data.txt

Esto dice para cada línea, si coincide con "CLAVE", realice un j oin de la siguiente línea. Después de que complete el mandato (en contra de todas las líneas), emitir una w rito y q uit.


4

Si Perl es una opción, puedes probar:

perl -0pe 's/(.*)\n(.*)\n/$1 $2\n/g' file.txt

¿ -0Le dice a Perl que establezca el separador de registros ( $/)en nulo, para que podamos abarcar varias líneas en nuestro patrón de coincidencia. Las páginas de manual son un poco demasiado técnicas para que yo pueda entender lo que significa en la práctica.
Sridhar Sarnobat

4

Puede usar awk como este para combinar cada 2 pares de líneas:

awk '{ if (NR%2 != 0) line=$0; else {printf("%s %s\n", line, $0); line="";} } \
     END {if (length(line)) print line;}' flle

4

Otras soluciones que usan vim (solo como referencia).

Solución 1 :

Abra el archivo en vim vim filename, luego ejecute el comando:% normal Jj

Este comando es muy fácil de entender:

  • %: para todas las líneas,
  • normal: ejecuta el comando normal
  • Jj: ejecuta el comando Unir, luego salta a la línea inferior

Después de eso, guarde el archivo y salga con :wq

Solución 2 :

Ejecute el comando en shell, vim -c ":% normal Jj" filenameluego guarde el archivo y salga con :wq.


También norm!más robusto que normalen caso de Jser reasignado. +1 para la solución vim.
qeatzy

@ qeatzy Gracias por enseñarme eso. Muy contento de saberlo. ^ _ ^
Jensen

3

También puede usar el siguiente comando vi:

:%g/.*/j

O incluso :%g//jdado que todo lo que necesita es una coincidencia para que se ejecute la unión , y una cadena nula sigue siendo una expresión regular válida.
ghoti

1
@ghoti, en Vim, cuando se usa solo //, se usará el patrón de búsqueda anterior. Si no hay un patrón anterior, Vim simplemente informa un error y no hace nada. La solución de Jdamian funciona todo el tiempo.
Tzunghsing David Wong

1
@TzunghsingDavidWong: es un buen puntero para los usuarios de vim. Prácticamente para mí, ni la pregunta ni esta respuesta mencionaron vim.
ghoti

3

Una ligera variación en la respuesta de glenn jackman usando paste: si el valor de la -dopción delimitador contiene más de un carácter, pasterecorre los caracteres uno por uno, y combinado con las -sopciones sigue haciéndolo mientras procesa el mismo archivo de entrada.

Esto significa que podemos usar lo que queramos tener como separador más la secuencia de escape \npara fusionar dos líneas a la vez.

Usando una coma:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string,1
KEY 4192:1349 string,1
KEY 7329:2407 string,2
KEY 0:1774 string,1

y el signo de dólar:

$ paste -s -d '$\n' infile
KEY 4048:1736 string$3
KEY 0:1772 string$1
KEY 4192:1349 string$1
KEY 7329:2407 string$2
KEY 0:1774 string$1

Lo que esto no puede hacer es usar un separador que consta de varios caracteres.

Como pastebeneficio adicional, si es compatible con POSIX, esto no modificará la nueva línea de la última línea del archivo, por lo que para un archivo de entrada con un número impar de líneas como

KEY 4048:1736 string
3
KEY 0:1772 string

paste no agregará el carácter de separación en la última línea:

$ paste -s -d ',\n' infile
KEY 4048:1736 string,3
KEY 0:1772 string

1
nawk '$0 ~ /string$/ {printf "%s ",$0; getline; printf "%s\n", $0}' filename

Esto se lee como

$0 ~ /string$/  ## matches any lines that end with the word string
printf          ## so print the first line without newline
getline         ## get the next line
printf "%s\n"   ## print the whole line and carriage return

1

En el caso donde necesitaba combinar dos líneas (para un procesamiento más fácil), pero permitir que los datos pasen los específicos, encontré que esto es útil

data.txt

string1=x
string2=y
string3
string4
cat data.txt | nawk '$0 ~ /string1=/ { printf "%s ", $0; getline; printf "%s\n", $0; getline } { print }' > converted_data.txt

la salida se ve así:

convert_data.txt

string1=x string2=y
string3
string4

1

Otro enfoque con vim sería:

:g/KEY/join

Esto aplica un join(a la línea debajo de él) a todas las líneas que tienen la palabra KEY. Resultado:

KEY 4048:1736 string 3
KEY 0:1772 string 1
KEY 4192:1349 string 1
KEY 7329:2407 string 2
KEY 0:1774 string 1

0

La manera más simple es aquí:

  1. Elimine las líneas pares y escríbalas en algún archivo temporal 1.
  2. Elimine las líneas impares y escríbalas en algún archivo temporal 2.
  3. Combine dos archivos en uno utilizando el comando pegar con -d (significa eliminar espacio)

sed '0~2d' file > 1 && sed '1~2d' file > 2 && paste -d " " 1 2

0
perl -0pE 's{^KEY.*?\K\s+(\d+)$}{ $1}msg;' data.txt > data_merged-lines.txt

-0engulle todo el archivo en lugar de leerlo línea por línea;
pEenvuelve el código con el bucle e imprime la salida, consulte los detalles en http://perldoc.perl.org/perlrun.html ;
^KEYcoincide con "KEY" al comienzo de la línea, seguido de una coincidencia no codiciosa de nada ( .*?) antes de la secuencia de

  1. uno o más espacios \s+de cualquier tipo, incluidos los saltos de línea;
  2. uno o más dígitos (\d+)que capturamos y luego volvemos a insertar como $1;

seguido por el final de la línea $.

\Kexcluye convenientemente todo en su lado izquierdo de la sustitución, por lo que { $1}reemplaza solo la secuencia 1-2, consulte http://perldoc.perl.org/perlre.html .


0

Una solución más general (permite unir más de una línea de seguimiento) como un script de shell. Esto agrega una línea entre cada uno, porque necesitaba visibilidad, pero eso se soluciona fácilmente. Este ejemplo es donde terminó la línea "clave": y ninguna otra línea lo hizo.

#!/bin/bash
#
# join "The rest of the story" when the first line of each   story
# matches $PATTERN
# Nice for looking for specific changes in bart output
#

PATTERN='*:';
LINEOUT=""
while read line; do
    case $line in
        $PATTERN)
                echo ""
                echo $LINEOUT
                LINEOUT="$line"
                        ;;
        "")
                LINEOUT=""
                echo ""
                ;;

        *)      LINEOUT="$LINEOUT $line"
                ;;
    esac        
done

-1

Pruebe la siguiente línea:

while read line1; do read line2; echo "$line1 $line2"; done <old.txt>new_file

Poner delimitador en el medio

"$line1 $line2";

por ejemplo, si el delimitador es |, entonces:

"$line1|$line2";

Esta respuesta no agrega nada no proporcionado en la respuesta de Hai Vu que se publicó 4 años antes que la suya.
Fedorqui 'así que deja de dañar'

Estoy parcialmente de acuerdo, trato de agregar una explicación y más genérico. No editará el archivo antiguo también. Gracias por su sugerencia
Suman

-2

Puedes usar xargsasí:

xargs -a file

% cat> file abc% xargs -a file abc% Funciona para mí
RSG

Hace algo, sí, pero no lo que pidió el OP. Específicamente, une tantas líneas como sea posible. En realidad, podría obtener lo que desea, xargs -n 2pero esta respuesta no explica esto en absoluto.
tripleee
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.