Remove caracteres duplicados no bash

5

Se eu estou tendo uma linha como:

There are seven pencil

Eu quero imprimir isso como:

Ther a svn pcil

O que é o comando bash shell para isso?

Esclarecimento: o objetivo é remover todas, pelo menos, duas vezes, exceto a primeira ocorrência.

    
por Kishan 24.01.2018 / 22:27

8 respostas

10

Com base na sed sintaxe clássica s/replace-this/with-that/g onde g significa global replace = todas as ocorrências, alguém pode usar 2g em vez de g , o que significa global replacement but after second occurence (essa é uma extensão gnu sed ).

Exemplo que remove apenas e :

$ echo $a
there are seven pencil

$ echo $a | sed 's/e//2g'
ther ar svn pncil

Para remover todas as letras duplicadas, podemos fazer um truque como este:

$ sed -f <(printf 's/%s//2g\n' {a..z}) <<<"$a"
ther a svn pcil

Infelizmente, isso não funcionará: sed 's/[a-z]//2g'

O truque acima usa a substituição de processo <( ) , que pode ser usada como arquivo.

No meu processo de solução, a substituição é tratada como um arquivo de script sed , alimentado pelos comandos sed by -f option = read sed de um arquivo.

    
por 24.01.2018 / 22:58
5
Solução

Awk (para casos com distinção entre maiúsculas e minúsculas):

s="There are seven pencil"
printf '%s\n' "$s" | awk -v FS="" '{ 
           for(i=1; i<=NF; i++) 
               if ($i==" " || !a[$i]++) printf "%s", $i; print "" 
       }'
  • -v FS="" - defina o separador de campo "vazio" para que cada caractere se torne um campo separado (não POSIX, mas uma extensão GNU suportada por algumas implementações)
  • for(i=1; i<=NF; i++) - iterando sobre caracteres
  • if ($i==" " || !a[$i]++) - se é o caractere de espaço ou a primeira ocorrência de um determinado caractere

A saída:

Ther a svn pcil

Para maiúsculas e minúsculas, substitua a[$i] por a[tolower($i)] .

    
por 24.01.2018 / 22:37
3

Aqui está o próprio Bash.

s="There are seven pencil"
declare -A A
while IFS= read -rn1 a; do
 [ -z "$a" ] || [ -n "${A[$a]}" ] && continue
 printf %s "$a"
 [ "$a" == " " ] || A[$a]=x
done <<<"$s"
echo

Explicação linha por linha:

  1. Atribuir a string a uma variável

    s="There are seven pencil"
    
  2. Declare uma matriz associativa A

    declare -A A
    
  3. Este é um pouco complexo. Mas sem detalhes, ele lê a string por caracteres simples e atribui o caractere que acabou de ler para a . É um loop while .

    while IFS= read -rn1 a; do
    
  4. Continue o loop (vá para a próxima iteração, leia o próximo caractere) se o caractere atual estiver vazio ( [ -z "$a" ] ) ou ( || ) se o valor associado a essa chave (esse caractere) já tiver sido set (se tiver aqui vem novamente, por isso continuamos sem imprimi-lo).

    [ -z "$a" ] || [ -n "${A[$a]}" ] && continue
    
  5. Imprimir o personagem atual.

    printf %s "$a"
    
  6. Se o caractere for espaço, então não execute A[$a]=x - é o que significa || aqui. A[$a]=x é a operação de associação. Para evitar todos os espaços, nenhum valor deve ser atribuído ao espaço de chave na matriz A . (Ver ponto 4.)

    [ "$a" == " " ] || A[$a]=x
    
  7. Isso finaliza o loop while . <<<"$s" é um redirecionamento here-string. Alimenta o loop com a string.

    done <<<"$s"
    
  8. Este último echo imprime o separador de linha. printf no ponto 5. imprimiu apenas o caractere. Sem esse echo , a saída apareceria na mesma linha que o seguinte prompt de shell. Remova e veja você mesmo.

    echo
    
por 25.01.2018 / 00:13
2

Para remover todos os caracteres duplicados (mantendo apenas a primeira ocorrência), em zsh :

$ s="There are seven pencils"
$ printf '%s\n' ${(j::)${(s::u)s}}
Ther asvnpcil

Essa abordagem não pode ser usada se você quiser excluir alguns caracteres dessa deduplicação (como o caractere de espaço em seu exemplo).

  • s:: dividido na string vazia para dividir a string em seus constituintes de caracteres como algumas awk implementações fazem com% vazioFS
  • u : unique: remove duplicados na matriz resultante
  • j:: : junte os elementos da matriz com sequências vazias no meio.
por 25.01.2018 / 14:27
2

Python solução:

remove_dups.py script:

import sys

s, res = set(), []
for c in sys.argv[1]:    # iterating over characters
    if c not in s:       # on the 1st occurrence of a character
        res.append(c)
        if not c.isspace(): s.add(c)
print(''.join(res))      # print unique chars

Uso:

s="There are seven pencil"
python remove_dups.py "$s"

A saída:

Ther a svn pcil
    
por 24.01.2018 / 23:09
2

Outra solução sed :

  • para caracteres alfabéticos únicos:

    $ echo 'here hear' | sed 's/\(\([[:alpha:]]\).*\)/'
    here ear
    
  • para todos eles, o sinal g não ajudará, pois os caracteres já processados não serão verificados novamente. Então, use um loop - 't' irá ramificar para o rótulo, desde que a substituição tenha sucesso

    $ echo There are seven pencils | sed -e :a -e 's/\(\([[:alpha:]]\).*\)//; ta'
    Ther a svn pcil
    

    Com o GNU sed , você pode encurtá-lo para:

    sed -E ':a;s/(([[:alpha:]]).*)/;ta'
    

para ignorar maiúsculas e minúsculas (ainda com GNU sed ):

$ echo 'There this That' | sed -E ':a; s/(([[:alpha:]]).*)//i; ta'
Ther is a


com perl (aqui limitado apenas a letras ASCII):

$ echo 'There are seven pencil' | perl -pe 'while(s/([a-zA-Z]).*?\K//g){}'
Ther a svn pcil
$ echo 'There this That' | perl -pe 'while(s/([a-z]).*?\K//gi){}'
Ther is a
    
por 25.01.2018 / 04:39
0

Uma solução awk

Conteúdo do arquivo de entrada file.txt :

There are seven pencil

Para cada linha de file.txt , crie uma nova string. Coloque os caracteres da linha original na nova seqüência, um caractere por vez, se eles já não tiverem ocorrido nessa linha. Sempre inclua caracteres de espaço. Imprima a nova string quando toda a linha tiver sido processada:

awk '{
       printme="" ;
       for ( n=1 ; n<=length($0) ; n++ ) {
         char=substr($0,n,1) ;
         if ( printme !~ char || char == " " ) printme = printme char ;
       } ;
       print printme ;
     }' "file.txt"

Saída:

Ther a svn pcil
    
por 25.01.2018 / 05:33
0

Podemos fazer isso abaixo do comando sed simples com loop

input.txt

There are seven pencil

comando:

 for i in {a..z}; do sed -i "s/$i//2g" input.txt; done

saída

Ther a svn pcil
    
por 25.01.2018 / 10:23