Comment supprimer des mots particuliers des lignes d'un fichier texte?


13

mon fichier texte ressemble à ceci:

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

maintenant je veux supprimer Liquid penetration 95% mass (m)de mes lignes pour obtenir uniquement les valeurs. Comment dois-je procéder?


3
simplementgrep -o '[^[:space:]]\+$' file
Avinash Raj

@AvinashRaj: Pour le moment, cette solution obtient la «médaille du mastic» :)
pa4080

2
@ pa4080 Au moins pour l'entrée que j'ai testée (10 millions de lignes), l' approche générale d' Avinash Raj peut être rendue plus rapide d'un ordre de grandeur en utilisant PCRE. (Je pourrais confirmer que le moteur, pas le modèle, est responsable, comme GNU grep l'accepte \S+$avec -Eou -P.) Ce type de solution n'est donc pas intrinsèquement lent. Mais je n'arrive toujours pas à me rapprocher de la cutméthode d' αғsнιη , qui a également gagné votre référence .
Eliah Kagan

Réponses:


22

S'il n'y a qu'un seul =signe, vous pouvez tout supprimer avant et y compris =comme ceci:

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

Si vous souhaitez modifier le fichier d'origine, utilisez l' -ioption après le test:

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

Remarques

  • -rutiliser ERE afin que nous n'ayons pas à nous échapper (et)
  • s/old/newremplacer oldparnew
  • .* un nombre quelconque de caractères
  • (things)enregistrer thingspour plus tard avec référence arrière \1, \2etc.

Merci ça a marché. J'ai utilisé cette commande pour écraser le fichier existant: sed -i -r 's /.*= (. *) / \ 1 /' time.txt Pouvez-vous expliquer comment cela fonctionne?
OE

Pourquoi ne pas éviter la rétro-référence? s/^.*= //fonctionnerait tout aussi bien, car la valeur correcte est à la fin de la ligne.
jpaugh

@jpaugh Eh bien en partie parce qu'il est trop tard pour changer ma réponse qui a été la première publiée - d'autres ont déjà donné la solution que vous mentionnez et d'autres moyens plus efficaces pour ce cas :) Mais peut-être que montrer comment utiliser \1etc. a une certaine valeur pour les personnes qui atterrir sur cette question lors de la recherche, qui n'ont pas un problème aussi simple
Zanna

@Zanna C'est plus général, au moins.
jpaugh

21

C'est un travail pour awk; en supposant que les valeurs n'apparaissent que dans le dernier champ (selon votre exemple):

awk '{print $NF}' file.txt
  • NFest une awkvariable, s'étend au nombre de champs dans un enregistrement (ligne), donc $NF(notez le $devant) contient la valeur du dernier champ.

Exemple:

% 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

13

J'ai décidé de comparer les différentes solutions, listées ici. À cet effet, j'ai créé un gros fichier, basé sur le contenu fourni par l'OP:

  1. J'ai créé un fichier simple, nommé 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. Ensuite, j'ai exécuté cette boucle:

    for i in {1..100}; do cat input.file | tee -a input.file; done
    
  3. La fenêtre du terminal a été bloquée. J'ai exécuté killall teedepuis un autre terminal. J'ai ensuite examiné le contenu du fichier par les commandes: less input.fileet cat input.file. Ça avait l'air bien, sauf la dernière ligne. J'ai donc supprimé la dernière ligne et créé une copie de sauvegarde: cp input.file{,.copy}(à cause des commandes qui utilisent l' option inplace ).

  4. Le nombre final de lignes dans le fichier input.fileest de 2 192 473 . J'ai obtenu ce numéro par la commande wc:

    $ cat input.file | wc -l
    2192473
    

Voici le résultat de la comparaison:

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

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

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

    Alternativement, si nous redirigeons la sortie vers un nouveau fichier, la commande est plus rapide:

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

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

    $ time rev input.file | coupe -d '' -f1 | rev> output.file
    
    réel 0m3.703s
    utilisateur 0m2.108s
    sys 0m4.916s
    
  • grep -oP '.*= \K.*'

    $ time grep -oP '. * = \ K. *' input.file> output.file
    
    réel 0m3.328s
    utilisateur 0m3.252s
    sys 0m0.072s
    
  • sed 's/.*= //' (respectivement, l' -ioption rend la commande plusieurs fois plus lente)

    $ time sed 's /.*= //' input.file> output.file
    
    réel 0m3.310s
    utilisateur 0m3.212s
    sys 0m0.092s
    
  • perl -pe 's/.*= //' (l' -ioption ne produit pas de grande différence de productivité ici)

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

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

    $ time cut -c 35- fichier d'entrée> fichier de sortie
    
    réel 0m0.352s
    utilisateur 0m0.284s
    sys 0m0.064s
    
  • cut -d= -f2

    $ time cut -d = -f2 fichier d'entrée> fichier de sortie
    
    réel 0m0.328s
    utilisateur 0m0.260s
    sys 0m0.064s
    

La source de l'idée.


2
donc ma cut -d= -f2solution gagne. haha
αғsнιη

Pouvez-vous donner plus d'informations sur la façon dont vous avez créé ce fichier? De plus, comment wc -lproduit trois nombres? Lorsqu'aucune autre option n'est transmise, l' -loption doit supprimer tout sauf le nombre de lignes.
Eliah Kagan du

@EliahKagan, c'est fait. J'ai mis à jour la réponse.
pa4080

Ah, je vois - les espaces étaient des séparateurs de groupes de chiffres. (Avait-il wcréellement affiché ces espaces? Existe-t-il des paramètres régionaux pour lesquels il fera cela?) Merci pour la mise à jour!
Eliah Kagan

@EliahKagan: Enfin, j'ai lu vos questions wcune fois de plus. Je ne sais pas où étaient mes esprits tôt aujourd'hui, mais je ne pouvais vraiment pas les comprendre. Donc, en effet, les espaces étaient des séparateurs de groupes de chiffres , et wcne les ajoutent pas :)
pa4080

12

Avec grepet -Ppour avoir PCRE(interpréter le motif en tant que P erl- C ompatible R REGULIERS E xpression) et -od'imprimer identifié motif seul. La \Knotification ignorera la partie correspondante avant elle-même.

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

Ou vous pouvez utiliser la cutcommande à la place.

cut -d= -f2 infile

2
En plus d'exécuter la plus rapide de toutes les méthodes testées dans le test de référence du pa4080 , la cutméthode de cette réponse a également été clairement gagnante dans un test de référence plus petit que j'ai exécuté qui a testé moins de méthodes mais a utilisé un fichier d'entrée plus volumineux. C'était bien plus de dix fois plus rapide que la variante rapide de la méthode que j'aime personnellement (et que ma réponse concerne principalement).
Eliah Kagan

11

Étant donné que le préfixe de ligne a toujours la même longueur (34 caractères), vous pouvez utiliser cut:

cut -c 35- < input.txt > output.txt

6

Inversez le contenu du fichier avec rev, canalisez la sortie cutavec un espace comme délimiteur et 1 comme champ cible, puis inversez-le à nouveau pour obtenir le numéro d'origine:

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

5

C'est simple, court et facile à écrire, à comprendre et à vérifier, et je l'aime personnellement:

grep -oE '\S+$' file

grepdans Ubuntu , lorsqu'il est invoqué avec -Eou -P, prend le raccourci \s pour signifier un caractère d'espace blanc (en pratique, généralement un espace ou une tabulation) et \Spour signifier tout ce qui n'en est pas un. En utilisant le quantificateur+ et l'ancre de fin de ligne$ , le motif \S+$correspond à un ou plusieurs espaces non à la fin d'une ligne . Vous pouvez utiliser à la -Pplace de -E; dans ce cas, la signification est la même, mais un moteur d'expressions régulières différent est utilisé, de sorte qu'elles peuvent avoir des caractéristiques de performances différentes .

C'est équivalent à la solution commentée d'Avinash Raj (juste avec une syntaxe plus simple et plus compacte):

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

Ces approches ne fonctionneront pas s'il peut y avoir des espaces de fin après le nombre. Ils peuvent être modifiés comme ils le font, mais je ne vois aucun intérêt à y entrer ici. Bien qu'il soit parfois instructif de généraliser une solution pour travailler dans plus de cas, il n'est pas pratique de le faire presque aussi souvent que les gens ont tendance à le supposer, car on n'a généralement aucun moyen de savoir de quelles manières incompatibles différentes le problème pourrait finalement avoir à résoudre. être généralisé.


La performance est parfois une considération importante. Cette question ne stipule pas que l'entrée est très grande, et il est probable que chaque méthode qui a été publiée ici est assez rapide. Cependant, si la vitesse est souhaitée, voici un petit point de repère sur un fichier d'entrée de dix millions de lignes:

$ 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

Je l'ai exécuté deux fois au cas où l'ordre importait (comme c'est parfois le cas pour les tâches lourdes d'E / S) et parce que je n'avais pas de machine disponible qui ne faisait pas d'autres choses en arrière-plan qui pourraient fausser les résultats. De ces résultats, je conclus ce qui suit, au moins provisoirement et pour les fichiers d'entrée de la taille que j'ai utilisée:

  • Hou la la! Passer -P(pour utiliser PCRE ) plutôt que -G(par défaut quand aucun dialecte n'est spécifié) ou -Erendu grepplus rapide de plus d'un ordre de grandeur. Ainsi, pour les fichiers volumineux, il peut être préférable d'utiliser cette commande que celle illustrée ci-dessus:

    grep -oP '\S+$' file
  • SENSATIONNEL!! La cutméthode dans la réponse de αғsнιη , est sur un ordre de grandeur plus rapide que même la version plus rapide de mon chemin! C'était également le gagnant du benchmark pa4080 , qui couvrait plus de méthodes que cela mais avec une entrée plus petite - et c'est pourquoi je l'ai choisi, parmi toutes les autres méthodes, à inclure dans mon test. Si les performances sont importantes ou si les fichiers sont énormes, je pense que la méthode de αғsнιη devrait être utilisée.cut -d= -f2 filecut

    Cela sert également à rappeler que le simple cutet les pasteutilitaires ne doivent pas être oubliés , et devraient peut-être être préférés le cas échéant, même s'il existe des outils plus sophistiqués comme grepcelui-ci sont souvent proposés en tant que solutions de première ligne (et que je suis personnellement plus habitué à utiliser).


4

perl- s ubstitute le modèle /.*= /avec une chaîne vide //:

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 - remplacez le motif par une chaîne vide:

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

ou (mais plus lent que ci-dessus) :

sed -i.bak 's/.*= //' input.file
  • Je mentionne cette approche, car elle est quelques fois plus rapide que celles de la réponse de Zanna .

gawk- remplacez le motif ".*= "par une chaîne vide "":

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...
    
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.