Listar strings que são substrings de outras strings na lista

7

Eu tenho uma lista de nomes assim:

dog_bone
dog_collar
dragon
cool_dragon
lion
lion_trainer
dog

Eu preciso extrair nomes que aparecem em outros nomes assim:

dragon
lion
dog

Eu examinei a página uniq man, mas parece comparar linhas inteiras, não seqüências de caracteres. Existe uma maneira de fazer isso com uma função bash?

    
por Question Overflow 07.05.2014 / 15:52

5 respostas

5
file=/the/file.txt
while IFS= read -r string; do
  grep -Fe "$string" < "$file" | grep -qvxFe "$string" &&
    printf '%s\n' "$string"
done < "$file"

Isso executa um read , dois grep e algumas vezes um printf de comandos por linha do arquivo, portanto não será muito eficiente.

Você pode fazer a coisa toda em uma invocação de awk :

awk '{l[NR]=$0}
     END {
       for (i=1; i<=NR; i++)
         for (j=1; j<=NR; j++)
           if (j!=i && index(l[j], l[i])) {
             print l[i]
             break
           }
     }' < "$file"

embora isso signifique que o arquivo inteiro está armazenado na memória.

    
por 07.05.2014 / 16:10
5

bash

names=(
  dog_bone
  dog_collar
  dragon
  cool_dragon
  lion
  lion_trainer
  dog
)

declare -A contained                 # an associative array
for (( i=0; i < ${#names[@]}; i++ )); do 
    for (( j=0; j < ${#names[@]}; j++ )); do 
        if (( i != j )) && [[ ${names[i]} == *"${names[j]}"* ]]; then
            contained["${names[j]}"]=1
        fi 
    done
done
printf "%s\n" "${!contained[@]}"    # print the array keys
dog
dragon
lion
    
por 07.05.2014 / 17:01
3

Aqui está uma abordagem Perl. Isso também precisa carregar o arquivo na memória:

perl -le '@f=<>; foreach $l1 (@f){ 
                    chomp($l1); 
                    foreach $l2 (@f){ 
                        chomp($l2); 
                        next if $l1 eq $l2; 
                        $k{$l1}++ if $l2=~/$l1/;
                    }
                } print join "\n", keys %k' file
    
por 07.05.2014 / 16:48
3

Uma maneira hacky de fazer o que você quer. Eu não tenho certeza se todos os seus exemplos incluirão um sublinhado ou não, mas você pode separar isso e usar sort | uniq -d para produzir uma lista de substrings que estão presentes mais de uma vez dentro de um determinado arquivo, usando o próprio arquivo como uma lista de sequências fixas para grep , através da opção -F .

Exemplo

$ grep -oFf <(grep -v _ file.txt) file.txt |
    LC_ALL=C sort | LC_ALL=C uniq -d    
dog
dragon
lion

O acima funciona da seguinte forma.

  1. <(grep -v _ file.txt) produzirá uma lista do conteúdo de file.txt omitindo as linhas que contêm um sublinhado ( _ ).

    $ grep -v _ file.txt 
    dragon
    lion
    dog
    
  2. grep -oFf <(..) file.txt usará os resultados de # 1 como uma lista de strings de comprimento fixo que grep encontrará no arquivo file.txt .

    $ grep -oFf <(grep -v _ file.txt) file.txt
    dog
    dog
    dragon
    dragon
    lion
    lion
    dog
    
  3. Os resultados desse comando são executados por meio do sort & uniq -d comandos que listarão as entradas que ocorrem mais de uma vez entre os resultados que grep -oFf produziu.

OBSERVAÇÃO: Se você quiser entender por que precisa usar o LC_ALL=C ao executar as chamadas sort e uniq , dê uma olhada na resposta do @Stone para isso aqui: O que faz "LC_ALL = C"? .

    
por 07.05.2014 / 20:10
3

Aqui está uma bash versão 4.x solução:

#!/bin/bash

declare -A output
readarray input < '/path/to/file'

for i in "${input[@]}"; do
  for j in "${input[@]}"; do
    [[ $j = "$i" ]] && continue
    if [ -z "${i##*"$j"*}" ]; then
      if [[ ! ${output[$j]} ]]; then
        printf "%s\n" "$j"
        output[$j]=1
      fi
    fi
  done
done
    
por 07.05.2014 / 16:57