Como remover a linha se ela contiver um caractere exatamente uma vez

10

Eu quero remover uma linha de um arquivo que contém um caractere específico apenas uma vez, se estiver presente mais de uma vez ou não estiver presente, mantenha a linha no arquivo.

Por exemplo:

DTHGTY
FGTHDC
HYTRHD
HTCCYD
JUTDYC

Aqui, o caractere que desejo remover é C , portanto, o comando deve remover as linhas FGTHDC e JUTDYC porque elas têm C exatamente uma vez.

Como posso fazer isso usando sed ou awk ?

    
por Namz 02.05.2017 / 06:59

8 respostas

20

Em awk , você pode definir o separador de campos como qualquer coisa. Se você definir como C , você terá tantos campos +1 quanto as ocorrências de C .

Então, se você disser awk -F'C' '{print NF}' <<< "C1C2C3" você recebe 4 : CCC consiste em 3 C s e, portanto, 4 campos.

Você deseja remover linhas nas quais C ocorre exatamente uma vez. Levando isso em consideração, no seu caso você vai querer remover as linhas nas quais existem exatamente dois C -fields. Então, basta ignorá-los:

$ awk -F'C' 'NF!=2' file
DTHGTY
HYTRHD
HTCCYD
    
por 02.05.2017 / 10:50
8

sed abordagem:

sed -i '/^[^C]*C[^C]*$/d' input
A opção

-i permite a modificação de arquivos no local

/^[^C]*C[^C]*$/ - corresponde a linhas que contêm C apenas uma vez

d - exclua linhas correspondentes

    
por 02.05.2017 / 07:09
8

Isso pode ser feito com sed como:

Código:

sed '/C.*C/p;/C/d' file1

Resultados:

DTHGTY
HYTRHD
HTCCYD

Como?

  1. Corresponder e imprimir qualquer linha com pelo menos duas cópias de C via /C.*C/p
  2. Exclua qualquer linha com C via /C/d , isso inclui as linhas já impressas na etapa 1
  3. Padrão imprime o restante das linhas
por 02.05.2017 / 07:10
6

Isso remove as linhas com exatamente uma ocorrência de C.

grep -v '^[^C]*C[^C]*$' file

A expressão regular [^C] corresponde a um caractere que não é C (ou nova linha) e o operador de repetição (também conhecido como estrela de Kleene) * especifica zero ou mais repetições da expressão anterior.

A saída padrão de grep (e a maioria das outras ferramentas orientadas a texto) é para saída padrão; redirecionar para um novo arquivo e talvez movê-lo em cima do arquivo original, se é isso que você quer. A mesma regex pode ser usada com sed -i para edição no local:

sed -i '/^[^C]*C[^C]*$/d' file

(Em algumas plataformas, notavelmente * BSD incluindo o macOS, a opção -i requer um argumento, como -i '' .)

    
por 02.05.2017 / 07:21
4

A ferramenta POSIX para edições com script de um arquivo (em vez de imprimir o conteúdo modificado para saída padrão) é ex .

printf '%s\n' 'g/^[^C]*C[^C]*$/d' x | ex file.txt

Claro que você pode usar sed -i se a sua versão do Sed for compatível, saiba que não é portátil se você estiver escrevendo um script que deve ser executado em diferentes tipos de sistemas.

David Foerster perguntou nos comentários:

Is there a reason why you're using printf and not echo or something like ex -c COMMAND?

Resposta: Sim.

Para printf vs. echo , é uma questão de portabilidade; veja Por que o printf é melhor que o eco? E também é mais fácil intercalar novas linhas entre comandos usando printf .

Para printf ... | ex vs. ex -c ... , é uma questão de tratamento de erros. Para este comando específico, isso não importaria, mas em geral acontece; por exemplo, tente colocar

ex -c '%s/this pattern is not in the file/replacement text/g | x' filename

em um script. Compare com o seguinte:

printf '%s\n' '%s/no matching lines/replacement/g' x | ex file

O primeiro irá travar e aguardar entrada; o segundo sairá quando o EOF for recebido pelo comando ex , portanto, o script continuará. Existem alternativas alternativas, como s///e , mas elas não são especificadas pelo POSIX. Eu prefiro usar a forma portátil, mostrada acima.

Para o comando g , deve ser uma nova linha no final, e eu prefiro usar printf para agrupar os comandos em vez de incorporar uma nova linha entre aspas simples.

    
por 02.05.2017 / 07:15
2

Aqui estão algumas opções usando o perl.

Como você só está correspondendo a um único caractere, pode usar tr/C// (uma tradução, sem substituições) para retornar o número de correspondências de C :

perl -lne 'print if tr/C// != 1' file

Mais geralmente, se você quiser corresponder uma string com vários caracteres ou uma expressão regular, use:

perl -lne 'print if (@m = /C/g) != 1' file

Isso atribui as correspondências da expressão regular /C/g a uma lista @m e imprime linhas quando a duração dessa lista não é 1 .

A opção -i pode ser adicionada para editar "no local".

    
por 02.05.2017 / 11:27
2
sed -e '
  s/C/&/2;t   # when 2nd C matches skip processing and print
  /C/d        # either one C or no C, so delete on C
'

sed -e '
   /C/!b     # no C, skip processing and print
   /C.*C/!d  # not(at least 2 C) => 1 C => delete
'

perl -lne 's/C/C/g == 1 or print'
    
por 02.05.2017 / 09:11
1

Para qualquer pessoa que queira awk especificamente, eu ofereço

awk '/C[^C]*C/{next}//{print}'

pule a linha se corresponder ao padrão, imprima de outra forma. Você realmente não precisa de {print} , você pode usar // e impressão padrão, mas acho que está mais claro.

Meu primeiro pensamento foi usar egrep -v com o mesmo padrão, mas isso na verdade não responde à pergunta como foi feita.

    
por 02.05.2017 / 18:05