Conte o número de ocorrências de uma substring em uma string

5

Como posso contar o número de ocorrências de uma substring em uma string usando o Bash?

EXEMPLO:

Gostaria de saber quantas vezes essa subseqüência:

Bluetooth
         Soft blocked: no
         Hard blocked: no

... ocorre nessa string ...

0: asus-wlan: Wireless LAN
         Soft blocked: no
         Hard blocked: no
1: asus-bluetooth: Bluetooth
         Soft blocked: no
         Hard blocked: no
2: phy0: Wireless LAN
         Soft blocked: no
         Hard blocked: no
113: hci0: Bluetooth
         Soft blocked: no
         Hard blocked: no

OBSERVAÇÃO I: tentei várias abordagens com sed, grep, awk ... Nada parece funcionar quando temos strings com espaços e várias linhas.

OBSERVAÇÃO II: Sou um usuário do Linux e estou tentando uma solução que não envolva a instalação de aplicativos / ferramentas fora daqueles que normalmente são encontrados nas distribuições do Linux.

IMPORTANTE:

Eu gostaria de algo como o exemplo hipotético abaixo. Nesse caso, usamos duas variáveis do shell (Bash) .

EXEMPLO:

STRING="0: asus-wlan: Wireless LAN
         Soft blocked: no
         Hard blocked: no
1: asus-bluetooth: Bluetooth
         Soft blocked: no
         Hard blocked: no
2: phy0: Wireless LAN
         Soft blocked: no
         Hard blocked: no
113: hci0: Bluetooth
         Soft blocked: no
         Hard blocked: no"

SUB_STRING="Bluetooth
         Soft blocked: no
         Hard blocked: no"

awk -v RS='
Bluetooth
         Soft blocked: no
         Hard blocked: no
' 'NR==FNR{str=$0; next} {print gsub(str,"")}' "$STRING" "$SUB_STRING"

NOTA: Estamos usando o awk apenas para ilustrar!

    
por Eduardo Lucio 07.05.2018 / 17:10

5 respostas

6

com perl :

printf '%s' "$SUB_STRING" |
  perl -l -0777 -ne '
    BEGIN{$sub = <STDIN>}
    @matches = m/\Q$sub\E/g;
    print scalar @matches' <(printf '%s' "$STRING")

Com bash sozinho, você sempre pode fazer algo como:

s=${STRING//"$SUB_STRING"}
echo "$(((${#STRING} - ${#s}) / ${#SUB_STRING}))"

Isso é $s contém $STRING com todas as ocorrências de $SUB_STRING removidas. Descobrimos o número de $SUB_STRING s que foram removidos calculando a diferença no número de caracteres entre $STRING e $s e dividindo pelo comprimento de $SUB_STRING em si.

POSIXly, você poderia fazer algo como:

s=$STRING count=0
until
  t=${s#*"$SUB_STRING"}
  [ "$t" = "$s" ]
do
  count=$((count + 1))
  s=$t
done
echo "$count"
    
por 07.05.2018 / 17:53
3

Usando funções de processamento de strings, podemos fazer isso com o Perl da seguinte maneira:

 printf '%s\n' "$STRING" |
 perl -nse '
      $_ .= join "", <>;
      $k++ while ++($p = index($_, $s, $p));
      print $k, "\n" ;
 '    --     -s="$SUB_STRING" 

Explicação:

° load up the whole string in $_

°  index function will return the position of a substring in a string OTW returns -1

° progressively match the substring and use the position found as the starting position for the next search. 

°  all this while increment the counter $k depicting substring found. 

Alguns outros métodos estão listados abaixo:

Slurp a string e use regex.

printf '%s\n' "$STRING" |
perl -slp -0777e '
        $_ = () = /$s/g;
 ' -- -s="$s" 

° Slurp string na variável $ _.

° passa a substring da linha de comando para perl usando a opção -s.

° agora executa uma correspondência em $ _ e, em um contexto de lista, você recebe as correspondências que são obtidas no contexto escalar para obter o número de correspondências.

° a opção -p deve fazer automaticamente o que está em $ _.

Método usando a ferramenta sed:

 esc_s=$(printf '%s\n' "$SUB_STRING" |\
 sed -e 's:[][\/.^$*]:\&:g' -e 'H;1h;$!d;g;s/\n/\n/g')

 printf '%s\n' "$STRING" |
 sed -ne '
         $!{N;s/^/\n/;D;}
         /'"$esc_s"'/{
               x;p;x
               s///;s/^/\n/;D
         }
 ' | wc -l

° Como uma etapa preparatória, vamos em frente e escapamos de todos os personagens agindo como meta-caracteres para o lado esquerdo da instrução s /// na subseqüência, o que, se não for feito, causará a queda do sed.

° Agora nós slurp o todo da corda no espaço do teste padrão.

° então continuamos imprimindo uma linha vazia, o espaço de espera é um bom candidato e retiramos a substring do espaço de padrão.

° enxágüe ... espuma ... repita enquanto a substring estiver presente.

° as linhas vazias são então canalizadas para a ferramenta wc que nos dará a contagem de linhas = número de vezes que a substring foi encontrada.

Esta é a versão do shell:

 e=$STRING  N=0
 while 
     e=$(expr " $e" : " \(.*\)$SUB_STRING")
     case $e in "" ) break ;; esac
  do
           N=$(expr "$N" + 1)
  done
  echo "$N"
    
por 07.05.2018 / 22:38
2

Você pode usar o Python como pergunta

python -c 'print "abcdabcva".count("ab")'

Ou se você estiver trabalhando com variáveis de shell:

python -c 'print("""'"$STRING"'""".count("""'"$SUB_STRING"'"""))'

No seu caso:

python -c 'print """0: asus-wlan: Wireless LAN
                   Soft blocked: no
                   Hard blocked: no
          1: asus-bluetooth: Bluetooth
                   Soft blocked: no
                   Hard blocked: no
          2: phy0: Wireless LAN
                   Soft blocked: no
                   Hard blocked: no
          113: hci0: Bluetooth
                   Soft blocked: no
                   Hard blocked: no""".count("""Bluetooth
                   Soft blocked: no
                   Hard blocked: no""")'
    
por 08.05.2018 / 16:19
2
gawk '
END { print NR - 1 }
' RS='Bluetooth
         Soft blocked: no
         Hard blocked: no' input.txt

Explicação

RS - o separador de registro de entrada, por padrão, uma nova linha. Defina-o como a string necessária e awk dividirá todo o texto nos registros, usando essa string como separador. Em seguida, resta apenas imprimir o número de registros subtraídos por 1 na seção END .

Usando variáveis:

#!/bin/bash

STRING='0: asus-wlan: Wireless LAN
         Soft blocked: no
         Hard blocked: no
1: asus-bluetooth: Bluetooth
         Soft blocked: no
         Hard blocked: no
2: phy0: Wireless LAN
         Soft blocked: no
         Hard blocked: no
113: hci0: Bluetooth
         Soft blocked: no
         Hard blocked: no'

SUB_STRING='Bluetooth
         Soft blocked: no
         Hard blocked: no'

gawk 'END { print NR - 1 }' RS="$SUB_STRING" <<< "$STRING"
    
por 08.05.2018 / 22:40
1

Se você tiver gnugrep, você pode executar algo nos moldes de

grep -zPio 'Bluetooth\s+Soft blocked: no\s+Hard blocked: no' ex.txt | grep -zc .

    
por 08.05.2018 / 08:03