Como remover palavras específicas das linhas de um arquivo de texto?

13

meu arquivo de texto fica assim:

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

agora eu quero excluir Liquid penetration 95% mass (m)das minhas linhas para obter apenas os valores. Como devo fazer isso?

OE
fonte
3
simplesmentegrep -o '[^[:space:]]\+$' file
Avinash Raj
@AvinashRaj: Para o momento, esta solução tem a 'medalha de massa' :)
pa4080
2
@ pa4080 Pelo menos para a entrada que testei (10 milhões de linhas), a abordagem geral da Avinash Raj pode ser feita em uma ordem de magnitude mais rápida usando o PCRE. (Eu poderia confirmar que o mecanismo, não o padrão, é responsável, como o GNU grep aceita \S+$com um -Eou outro -P.) Portanto, esse tipo de solução não é inerentemente lento. Mas ainda não consigo chegar nem perto do cutmétodo de αғsнιη , que também ganhou sua referência .
Eliah Kagan

Respostas:

22

Se houver apenas um =sinal, você poderá excluir tudo antes e incluir =assim:

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

Se você deseja alterar o arquivo original, use a -iopção após o teste:

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

Notas

  • -ruse ERE para que não tenhamos que escapar (e)
  • s/old/newsubstitua oldpornew
  • .* qualquer número de caracteres
  • (things)salvar thingsa referência anterior mais tarde, com \1, \2, etc.
Zanna
fonte
Graças funcionou. Eu usei este comando para substituir o arquivo existente: sed -i -r 's /.*= (. *) / \ 1 /' time.txt Você pode explicar como ele funciona?
OE
Por que não evitar a referência posterior? s/^.*= //funcionaria igualmente bem, pois o valor correto está no final da linha.
jpaugh
@jpaugh Bem, em parte porque é tarde demais para mudar minha resposta, que foi a primeira postada - outros já deram a solução que você mencionou e outras maneiras mais eficientes para este caso :) Mas talvez mostrar como usar \1etc tenha algum valor para as pessoas que terra sobre esta questão durante a pesquisa, que não têm um problema tão simples
Zanna
@ Zanna É mais geral, pelo menos.
jpaugh
21

Este é um trabalho para awk; supondo que os valores ocorram apenas no último campo (conforme seu exemplo):

awk '{print $NF}' file.txt
  • NFé uma awkvariável, se expande para o número de campos em um registro (linha), portanto $NF(observe a $frente) contém o valor do último campo.

Exemplo:

% 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
heemail
fonte
13

Decidi comparar as diferentes soluções, listadas aqui. Para esse fim, criei um arquivo grande, com base no conteúdo fornecido pelo OP:

  1. Eu criei um arquivo simples, chamado 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. Então eu executei este loop:

    for i in {1..100}; do cat input.file | tee -a input.file; done
    
  3. A janela do terminal foi bloqueada. Eu executei killall teede outro terminal. Depois examinei o conteúdo do arquivo pelos comandos: less input.filee cat input.file. Parecia bom, exceto a última linha. Então, removi a última linha e criei uma cópia de backup: cp input.file{,.copy}(por causa dos comandos que usam a opção inplace ).

  4. A contagem final das linhas no arquivo input.fileé 2 192 473 . Eu recebi esse número pelo comando wc:

    $ cat input.file | wc -l
    2192473
    

Aqui está o resultado da comparação:

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

    $ time grep -o '[^ [: espaço:]] \ + $' input.file> output.file
    
    0m58.539s reais
    usuário 0m58.416s
    sys 0m0.108s
    
  • sed -ri 's/.* = (.*)/\1/'

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

    Como alternativa, se redirecionarmos a saída para um novo arquivo, o comando será mais rápido:

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

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

    $ time rev input.file | cut -d '' -f1 | rev> output.file
    
    0m3.703s reais
    usuário 0m2.108s
    sys 0m4.916s
    
  • grep -oP '.*= \K.*'

    $ time grep -oP '. * = \ K. *' input.file> output.file
    
    0m3.328s reais
    usuário 0m3.252s
    sys 0m0.072s
    
  • sed 's/.*= //' (respectivamente, a -iopção torna o comando algumas vezes mais lento)

    $ time sed 's /.*= //' input.file> output.file
    
    0m3.310s reais
    usuário 0m3.212s
    sys 0m0.092s
    
  • perl -pe 's/.*= //' (a -iopção não produz grande diferença na produtividade aqui)

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

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

    $ time cut -c 35- input.file> output.file
    
    0m0.352s reais
    usuário 0m0.284s
    sys 0m0.064s
    
  • cut -d= -f2

    $ time cut -d = -f2 input.file> output.file
    
    0m0.328s reais
    usuário 0m0.260s
    sys 0m0.064s
    

A fonte da ideia.

pa4080
fonte
2
então minha cut -d= -f2solução vence. haha
α Octsнιη 23/10
Você pode fornecer mais informações sobre como você criou este arquivo? Além disso, como wc -lgera três números? Quando nenhuma outra opção é aprovada, a -lopção deve suprimir tudo, exceto a contagem de linhas.
Eliah Kagan
@EliahKagan, done. Eu atualizei a resposta.
pa4080
Ah, entendo - os espaços eram separadores de grupos de dígitos. ( wcNa verdade, havia exibido esses espaços? Existem configurações de localidade para as quais ele fará isso?) Obrigado pela atualização!
Eliah Kagan
@EliahKagan: Finalmente, li suas perguntas mais wcuma vez. Não sei onde estava minha mente hoje, mas realmente não conseguia entendê-las. Então, na verdade, os espaços foram separadores de grupos de dígitos , e wcnão adicioná-los :)
pa4080
12

Com grepeo -Ppor ter PCRE(interpretar o padrão como um P erl- C ompatible R egular E xpression) e o -opadrão de impressão combinados sozinho. A \Knotificação ignorará a parte correspondente que vem antes de si mesma.

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

Ou você pode usar o cutcomando.

cut -d= -f2 infile
αғsнιη
fonte
2
Além de executar o mais rápido de todos os métodos testados no benchmark do pa4080 , o cutmétodo nesta resposta também foi o vencedor em um benchmark menor que executei que testou menos métodos, mas usou um arquivo de entrada maior. Foi bem mais de dez vezes mais rápido que a variante rápida do método que eu pessoalmente gosto (e que minha resposta é principalmente sobre).
Eliah Kagan
11

Como o prefixo da linha sempre tem o mesmo comprimento (34 caracteres), você pode usar cut:

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

Inverta o conteúdo do arquivo com rev, canalize a saída cutcom o espaço como delimitador e 1 como o campo de destino e, em seguida, inverta-o novamente para obter o número original:

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

É simples, curto e fácil de escrever, entender e verificar, e eu pessoalmente gosto:

grep -oE '\S+$' file

grepno Ubuntu , quando chamado com -Eou -P, usa a abreviação \s para significar um caractere de espaço em branco (na prática geralmente um espaço ou guia) e \Spara significar qualquer coisa que não seja um. Usando o quantificador+ e a âncora de fim de linha$ , o padrão \S+$corresponde a um ou mais não espaços em branco no final de uma linha . Você pode usar em -Pvez de -E; o significado nesse caso é o mesmo, mas um mecanismo diferente de expressões regulares é usado; portanto, eles podem ter características de desempenho diferentes .

Isso é equivalente à solução comentada de Avinash Raj (apenas com uma sintaxe mais fácil e mais compacta):

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

Essas abordagens não funcionarão se houver espaço em branco após o número. Eles podem ser modificados, mas não vejo sentido em entrar aqui. Embora às vezes seja instrutivo generalizar uma solução para funcionar em mais casos, não é prático fazê-lo com a frequência que as pessoas supõem, porque geralmente não há como saber de que maneiras diferentes e incompatíveis o problema pode precisar. ser generalizado.


Às vezes, o desempenho é uma consideração importante. Esta pergunta não estipula que a entrada seja muito grande e é provável que todos os métodos publicados aqui sejam rápidos o suficiente. No entanto, caso a velocidade seja desejada, aqui está uma pequena referência em um arquivo de entrada de dez milhões de linhas:

$ 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

Executei-o duas vezes, caso a ordem importasse (como às vezes é importante para tarefas pesadas de E / S) e porque não havia uma máquina disponível que não estivesse fazendo outras coisas em segundo plano que poderiam distorcer os resultados. A partir desses resultados, concluo o seguinte, pelo menos provisoriamente e para arquivos de entrada do tamanho que usei:

  • Uau! Passando -P(para usar PCRE ) em vez de -G(o padrão quando nenhum dialeto é especificado) ou -Etornado grepmais rápido em uma ordem de magnitude. Portanto, para arquivos grandes, pode ser melhor usar este comando do que o mostrado acima:

    grep -oP '\S+$' file
  • UAU!! O cutmétodo de resposta de αғsнιη , é mais de uma ordem de grandeza mais rápido do que até mesmo a versão mais rápida do meu caminho! Também foi o vencedor no benchmark do pa4080 , que abrangeu mais métodos do que isso, mas com menor contribuição - e é por isso que eu o escolhi, dentre todos os outros métodos, para incluir no meu teste. Se o desempenho é importante ou os arquivos são enormes, acho que o método de αғsнιη deve ser usado.cut -d= -f2 filecut

    Isso também serve como um lembrete de que o simples cute os pasteutilitários não devem ser esquecidos , e talvez devam ser preferidos quando aplicável, embora existam ferramentas mais sofisticadas como grepessas que são frequentemente oferecidas como soluções de primeira linha (e que eu pessoalmente estou mais acostumado para usar).

Eliah Kagan
fonte
4

perl- s ubstitute o padrão /.*= /com string vazia //:

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 - substitua o padrão por uma string vazia:

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

ou (mas mais lento que o acima) :

sed -i.bak 's/.*= //' input.file
  • Menciono essa abordagem, porque é poucas vezes mais rápida do que as da resposta de Zanna .

gawk- substitua o padrão ".*= "por uma string vazia "":

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
fonte