En mi trabajo de creación de una base de datos en inglés, a menudo trato con contenido de texto de diferentes fuentes y necesito fusionar líneas que comparten el mismo primer campo. A menudo pirateo esto en un editor de texto con una expresión regular que captura un primer campo, buscando en "\n", pero a menudo tengo archivos de texto> 10 GB, por lo que se prefiere una solución de transmisión de línea de comandos en memoria.
Ejemplo de entrada:
apple|pear apple|quince apple cider|juice banana|plantain cherry|cheerful, crimson cherry|ruddy cherry|ceriseSalida deseada:
apple|pear|quince apple cider|juice banana|plantain cherry|cheerful, crimson|ruddy|ceriseLa lógica es concatenar (unidas por "|") todas las líneas con el mismo primer campo.
El único delimitador es "|", y el delimitador solo aparece una vez por línea de entrada. es decir, es efectivamente un archivo de texto de 2 columnas. La clasificación de archivos no importa, la única preocupación son las líneas consecutivas con el primer campo idéntico.
Tengo muchas soluciones y frases ingeniosas (a menudo en awk o ruby) para procesar contenido de la misma línea, pero me encuentro con nudos cuando trato con varias líneas y agradecería ayuda. Por alguna razón, el procesamiento multilínea siempre me atasca.
Estoy seguro de que esto se puede hacer de manera sucinta con awk.
Suposiciones/entendidos:
Una idea awk :
awk ' function print_line() { if (prev != "") print prev,data } BEGIN { FS=OFS="|" } { if ($1 != prev) { print_line() prev=$1 data="" } gsub(/[[:space:]]+$/,"",$2) # strip trailing white space data= data (data=="" ? "" : OFS) $2 # concatentate 2nd fields with OFS="|" } END { print_line() } # flush last set of data to stdout ' pipe.datEsto genera:
apple|pear|quince apple cider|juice banana|plantain cherry|cheerful, crimson|ruddy|ceriseUsando cualquier awk en cualquier shell en cada cuadro de Unix y asumiendo que su entrada está agrupada por el primer campo como se muestra en su entrada de muestra y que realmente no tiene espacios en blanco al final de algunas líneas:
$ cat tst.awk BEGIN { FS=OFS="|" } $1 != prev { if ( NR>1 ) { print out } out = prev = $1 } { out = out OFS $2 } END { print out } $ awk -f tst.awk file apple|pear|quince apple cider|juice banana|plantain cherry|cheerful, crimson|ruddy|cerise Si no está agrupado, sort file | awk -f tst.awk y si hay espacios en blanco al final, agregue { sub(/ +$/,"") } como la primera línea del script.
Aquí hay una solución de Ruby que lee el archivo línea por línea. Al final, muestro cuánto más simple podría ser la solución si el archivo pudiera tragarse en una cadena.
Primero vamos a crear un archivo de entrada para trabajar.
str =<<~_ apple|pear apple|quince apple cider|juice banana|plantain cherry|cheerful, crimson cherry|ruddy cherry|cerise _ file_name_in = 'file_in' File.write(file_name_in, str) #=> 112Solución cuando el archivo se lee línea por línea
Podemos producir el archivo de salida deseado con el siguiente método.
def doit(file_name_in, file_name_out) fin = File.new(file_name_in, "r") fout = File.new(file_name_out, "w") str = '' until fin.eof? s = fin.gets.strip k,v = s.split(/(?=\|)/) if str.empty? str = s key = k elsif k == key str << v else fout.puts(str) str = s key = k end end fout.puts(str) fin.close fout.close endVamos a intentarlo.
file_name_out = 'file_out' doit(file_name_in, file_name_out) puts File.read(file_name_out)imprime lo siguiente.
apple|pear|quince apple cider|juice banana|plantain cherry|cheerful, crimson|ruddy|ceriseTenga en cuenta que
"apple|pear".split(/(?=\|)/) #=> ["apple", "|pear"] La expresión regular contiene la anticipación positiva (?=\|) que coincide con la ubicación de ancho cero entre 'e' y '|' .
Solución cuando el archivo se traga en una cadena
El OP no quiere engullir el archivo en una cadena (de ahí mi solución anterior), pero me gustaría mostrar cuánto más simple es el problema si pudiera hacerlo. Esta es una de las muchas formas de hacerlo.
def gulp_it(file_name_in, file_name_out) File.write(file_name_out, File.read(file_name_in).gsub(/^(.+)\|.*[^ ]\K *\r?\n\1/, '')) end gulp_it(file_name_in, file_name_out) #=> 98 puts File.read(file_name_out)huellas dactilares
apple|pear|quince apple cider|juice banana|plantain cherry|cheerful, crimson|ruddy cherry|cerisePensando en lo que hará el motor de expresiones regulares, esto puede ser aceptablemente rápido, dependiendo del tamaño del archivo, por supuesto.
Demostración de expresiones regulares
Mientras que el enlace usa el motor PCRE, el resultado sería el mismo usando el motor de expresiones regulares de Ruby (Onigmo). Podemos hacer que la expresión regular se autodocumente escribiéndola en modo de espacio libre .
/ ^ # match the beginning of a line (.+) # match one or more characters \|.*[^ ] # match '|', then zero or more chars, then a non-space \K # resets the starting point of the match and discards # any previously-matched characters [ ]* # match zero or more chars \r?\n # march the line terminator(s) \1 # match the content of capture group 1 /x # invoke free-spacing mode (.+) coincide con 'apple' , 'banana' y 'cherry' porque esas palabras están en las líneas iniciales. Uno podría escribir alternativamente ([^|]*) .