¿Cómo eliminar palabras particulares de líneas de un archivo de texto?

13

mi archivo de texto se ve así:

Liquid penetration 95% mass (m) = 0.000205348
Liquid penetration 95% mass (m) = 0.000265725
Liquid penetration 95% mass (m) = 0.000322823
Liquid penetration 95% mass (m) = 0.000376445
Liquid penetration 95% mass (m) = 0.000425341

ahora quiero eliminar Liquid penetration 95% mass (m)de mis líneas para obtener solo los valores. ¿Cómo debería hacerlo?

OE
fuente
3
simplementegrep -o '[^[:space:]]\+$' file
Avinash Raj
@AvinashRaj: Hasta el momento, esta solución obtiene la 'medalla de masilla' :)
pa4080
2
@ pa4080 Al menos para la entrada que probé (líneas 10M), el enfoque general de Avinash Raj se puede hacer un orden de magnitud más rápido utilizando PCRE. (Podría confirmar que el motor, no el patrón, es responsable, ya que GNU grep acepta \S+$con -Eo con -P.) Por lo tanto, este tipo de solución no es inherentemente lenta. Pero todavía no puedo acercarme al cutmétodo de αғsнιη , que también ganó su punto de referencia .
Eliah Kagan

Respuestas:

22

Si solo hay un =signo, puede eliminar todo antes e incluirlo =así:

$ sed -r 's/.* = (.*)/\1/' file
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341

Si desea cambiar el archivo original, use la -iopción después de probar:

sed -ri 's/.* = (.*)/\1/' file

Notas

  • -ruse ERE para que no tengamos que escapar (y)
  • s/old/newreemplazar oldconnew
  • .* cualquier número de caracteres
  • (things)salvar thingsa elementos detectados después con \1, \2, etc.
Zanna
fuente
Gracias funcionó. Utilicé este comando para sobrescribir el archivo existente: sed -i -r 's /.*= (. *) / \ 1 /' time.txt ¿Puede explicar cómo funciona?
OE
¿Por qué no evitar la referencia inversa? s/^.*= //funcionaría igualmente bien, ya que el valor correcto está al final de la línea.
jpaugh
@jpaugh Bueno, en parte porque es demasiado tarde para cambiar mi respuesta, que fue la primera publicada; otras ya han dado la solución que mencionas y otras formas más eficientes para este caso :) Pero tal vez mostrar cómo usar, \1etc. tiene algún valor para las personas que tierra sobre esta cuestión en la búsqueda, que no tienen un problema tan simple
Zanna
@Zanna Es más general, al menos.
jpaugh
21

Este es un trabajo para awk; suponiendo que los valores ocurran solo en el último campo (según su ejemplo):

awk '{print $NF}' file.txt
  • NFes una awkvariable, se expande al número de campos en un registro (línea), por lo tanto $NF(tenga $en cuenta el frente) contiene el valor del último campo.

Ejemplo:

% cat temp.txt 
Liquid penetration 95% mass (m) = 0.000205348
Liquid penetration 95% mass (m) = 0.000265725
Liquid penetration 95% mass (m) = 0.000322823
Liquid penetration 95% mass (m) = 0.000376445
Liquid penetration 95% mass (m) = 0.000425341

% awk '{print $NF}' temp.txt
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341
heemayl
fuente
13

Decidí comparar las diferentes soluciones, enumeradas aquí. Para este propósito, he creado un archivo grande, basado en el contenido proporcionado por el OP:

  1. Creé un archivo simple, llamado input.file:

    $ cat input.file
    Liquid penetration 95% mass (m) = 0.000205348
    Liquid penetration 95% mass (m) = 0.000265725
    Liquid penetration 95% mass (m) = 0.000322823
    Liquid penetration 95% mass (m) = 0.000376445
    Liquid penetration 95% mass (m) = 0.000425341
    
  2. Luego ejecuté este bucle:

    for i in {1..100}; do cat input.file | tee -a input.file; done
    
  3. La ventana del terminal estaba bloqueada. Ejecuté killall teedesde otra terminal. Luego examiné el contenido del archivo mediante los comandos: less input.filey cat input.file. Se veía bien, excepto la última línea. Así que eliminé la última línea y creé una copia de seguridad: cp input.file{,.copy}(debido a los comandos que usan la opción in situ ).

  4. El recuento final de las líneas en el archivo input.filees 2 192 473 . Obtuve ese número por el comando wc:

    $ cat input.file | wc -l
    2192473
    

Aquí está el resultado de la comparación:

  • grep -o '[^[:space:]]\+$'

    $ time grep -o '[^ [: espacio:]] \ + $' input.file> output.file
    
    0m58.539s reales
    usuario 0m58.416s
    sys 0m0.108s
    
  • sed -ri 's/.* = (.*)/\1/'

    $ time sed -ri 's /.* = (. *) / \ 1 /' input.file
    
    0m26.936 reales
    usuario 0m22.836s
    sys 0m4.092s
    

    Alternativamente, si redirigimos la salida a un nuevo archivo, el comando es más rápido:

    $ time sed -r 's /.* = (. *) / \ 1 /' input.file> output.file
    
    0m19.734s reales
    usuario 0m19.672s
    sys 0m0.056s
    
  • gawk '{gsub(".*= ", "");print}'

    $ time gawk '{gsub (". * =", ""); print}' input.file> output.file
    
    0m5.644s reales
    usuario 0m5.568s
    sys 0m0.072s
    
  • rev | cut -d' ' -f1 | rev

    $ time rev input.file | cortar -d '' -f1 | rev> output.file
    
    0m3.703s reales
    usuario 0m2.108s
    sys 0m4.916s
    
  • grep -oP '.*= \K.*'

    $ time grep -oP '. * = \ K. *' input.file> output.file
    
    0m3.328s reales
    usuario 0m3.252s
    sys 0m0.072s
    
  • sed 's/.*= //' (respectivamente, la -iopción hace que el comando sea un poco más lento)

    $ time sed 's /.*= //' input.file> output.file
    
    0m3.310s reales
    usuario 0m3.212s
    sys 0m0.092s
    
  • perl -pe 's/.*= //' (La -iopción no produce una gran diferencia en la productividad aquí)

    $ time perl -i.bak -pe 's /.*= //' input.file
    
    0m3.187s reales
    usuario 0m3.128s
    sys 0m0.056s
    
    $ time perl -pe 's /.*= //' input.file> output.file
    
    0m3.138s reales
    usuario 0m3.036s
    sys 0m0.100s
    
  • awk '{print $NF}'

    $ time awk '{print $ NF}' input.file> output.file
    
    0m1.251s reales
    usuario 0m1.164s
    sys 0m0.084s
    
  • cut -c 35-

    $ time cut -c 35- input.file> output.file
    
    0m0.352s reales
    usuario 0m0.284s
    sys 0m0.064s
    
  • cut -d= -f2

    $ time cut -d = -f2 input.file> output.file
    
    0m0.328s reales
    usuario 0m0.260s
    sys 0m0.064s
    

La fuente de la idea.

pa4080
fuente
2
entonces mi cut -d= -f2solución gana. jaja
αғsнιη
¿Puedes dar más información sobre cómo creaste este archivo? Además, ¿cómo da wc -lsalida a tres números? Cuando no se pasan otras opciones, la -lopción debe suprimir todo menos el recuento de líneas.
Eliah Kagan
@EliahKagan, hecho. He actualizado la respuesta.
pa4080
Ah, ya veo, los espacios eran separadores de grupos de dígitos. (¿ wcRealmente había mostrado esos espacios? ¿Hay configuraciones regionales para las que hará eso?) ¡Gracias por la actualización!
Eliah Kagan
@EliahKagan: Finalmente leí tus preguntas wcuna vez más. No sé dónde estaba mi ingenio hoy temprano, pero realmente no podía entenderlos. De hecho, los espacios eran separadores de grupos de dígitos , y wcno los agrega :)
pa4080
12

Con grepy el -Ppor tener PCRE(Interpretar el patrón como un P erl- C ompatible R egular E Xpression) y el -opatrón para imprimir emparejado solo. La \Knotificación ignorará la parte coincidente anterior a sí misma.

$ grep -oP '.*= \K.*' infile
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341

O podría usar el cutcomando en su lugar.

cut -d= -f2 infile
αғsнιη
fuente
2
Además de ejecutar el método más rápido de todos los probados en el benchmark de pa4080 , el cutmétodo en esta respuesta también fue el claro ganador en un benchmark más pequeño que ejecuté que probó menos métodos pero usó un archivo de entrada más grande. Fue bastante más de diez veces más rápido que la variante rápida del método que personalmente me gusta (y que mi respuesta es principalmente sobre).
Eliah Kagan
11

Como el prefijo de línea siempre tiene la misma longitud (34 caracteres), puede usar cut:

cut -c 35- < input.txt > output.txt
David Foerster
fuente
6

Invierta el contenido del archivo con rev, canalice la salida cutcon espacio como delimitador y 1 como campo de destino, luego inviértalo nuevamente para obtener el número original:

$ rev your_file | cut -d' ' -f1 | rev
0.000205348
0.000265725
0.000322823
0.000376445
0.000425341
f1nan
fuente
5

Esto es simple, breve y fácil de escribir, comprender y verificar, y personalmente me gusta:

grep -oE '\S+$' file

grepen Ubuntu , cuando se invoca con -Eo -P, toma la abreviatura \s para significar un carácter de espacio en blanco (en la práctica, generalmente un espacio o tabulación) y \Ssignifica cualquier cosa que no sea uno. Usando el cuantificador+ y el ancla de fin de línea$ , el patrón \S+$coincide con uno o más espacios en blanco al final de una línea . Puedes usar en -Plugar de -E; el significado en este caso es el mismo pero se usa un motor de expresiones regulares diferente , por lo que pueden tener características de rendimiento diferentes .

Esto es equivalente a la solución comentada de Avinash Raj (solo con una sintaxis más fácil y compacta):

grep -o '[^[:space:]]\+$' file

Estos enfoques no funcionan si no puede haber espacios en blanco después del número. Pueden modificarse para que lo hagan, pero no veo ningún punto en entrar en eso aquí. Aunque a veces es instructivo generalizar una solución para trabajar en más casos, no es práctico hacerlo con tanta frecuencia como la gente tiende a suponer, porque generalmente no hay forma de saber en cuál de las muchas formas incompatibles diferentes el problema podría necesitar. ser generalizado


El rendimiento es a veces una consideración importante. Esta pregunta no estipula que la entrada es muy grande, y es probable que cada método que se haya publicado aquí sea lo suficientemente rápido. Sin embargo, en caso de que se desee velocidad, aquí hay un pequeño punto de referencia en un archivo de entrada de diez millones de líneas:

$ perl -e 'print((<>) x 2000000)' file > bigfile
$ du -sh bigfile
439M    bigfile
$ wc -l bigfile
10000000 bigfile
$ TIMEFORMAT=%R
$ time grep -o '[^[:space:]]\+$' bigfile > bigfile.out
819.565
$ time grep -oE '\S+$' bigfile > bigfile.out
816.910
$ time grep -oP '\S+$' bigfile > bigfile.out
67.465
$ time cut -d= -f2 bigfile > bigfile.out
3.902
$ time grep -o '[^[:space:]]\+$' bigfile > bigfile.out
815.183
$ time grep -oE '\S+$' bigfile > bigfile.out
824.546
$ time grep -oP '\S+$' bigfile > bigfile.out
68.692
$ time cut -d= -f2 bigfile > bigfile.out
4.135

Lo ejecuté dos veces en caso de que el pedido importara (como a veces lo hace para tareas pesadas de E / S) y porque no tenía una máquina disponible que no estuviera haciendo otras cosas en segundo plano que pudieran sesgar los resultados. De esos resultados concluyo lo siguiente, al menos provisionalmente y para archivos de entrada del tamaño que utilicé:

  • ¡Guauu! Pasar -P(para usar PCRE ) en lugar de -G(el valor predeterminado cuando no se especifica un dialecto) o se -Ehace grepmás rápido en un orden de magnitud. Entonces, para archivos grandes, puede ser mejor usar este comando que el que se muestra arriba:

    grep -oP '\S+$' file
  • ¡¡GUAU!! El cutmétodo de la respuesta de αғsнιη , es más de un orden de magnitud más rápido que incluso la versión más rápida de mi camino! También fue el ganador en el punto de referencia de pa4080 , que cubrió más métodos que este pero con una entrada menor, y es por eso que lo elegí, de todos los otros métodos, para incluir en mi prueba. Si el rendimiento es importante o los archivos son enormes, creo que debería usarse el método de αғsнιη .cut -d= -f2 filecut

    Esto también sirve como un recordatorio de que lo simple cuty las pasteutilidades no deberían olvidarse , y tal vez deberían preferirse cuando corresponda, a pesar de grepque a menudo se ofrecen herramientas más sofisticadas como soluciones de primera línea (y que yo personalmente estoy más acostumbrado) a usar).

Eliah Kagan
fuente
4

perl- s sustituya el patrón /.*= /con una cadena vacía //:

perl -pe 's/.*= //' input.file > output.file
perl -i.bak -pe 's/.*= //' input.file
  • De perl --help:

    -e program        one line of program (several -e's allowed, omit programfile)
    -p                assume loop like -n but print line also, like sed
    -i[extension]     edit <> files in place (makes backup if extension supplied)
    

sed - sustituir el patrón con una cadena vacía:

sed 's/.*= //' input.file > output.file

o (pero más lento que el anterior) :

sed -i.bak 's/.*= //' input.file
  • Menciono este enfoque, porque es pocas veces más rápido que los de la respuesta de Zanna .

gawk- sustituir el patrón ".*= "con una cadena vacía "":

gawk '{gsub(".*= ", "");print}' input.file > output.file
  • De man gawk:

    gsub(r, s [, t]) For each substring matching the regular expression r in the string t,
                     substitute the string s, and return the number of substitutions. 
                     If t is not supplied, use $0...
    
pa4080
fuente