Подсчитать количество вхождений подстроки в строку - PullRequest
0 голосов
/ 07 мая 2018

Как подсчитать количество вхождений подстроки в строку, используя Bash?

Пример:

Я хотел бы знать, сколько раз эта подстрока ...

Bluetooth
         Soft blocked: no
         Hard blocked: no

... встречается в этой строке ...

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

ПРИМЕЧАНИЕ I: Я пробовал несколько подходов с помощью sed, grep, awk ... Кажется, ничего не работает, когда у нас есть строки с пробелами и несколькими строками.

ПРИМЕЧАНИЕ II: Я пользователь Linux и пытаюсь найти решение, которое не включает установку приложений / инструментов, отличных от тех, которые обычно находятся в дистрибутивах Linux.


ВАЖНО:

В дополнение к моему вопросу возможно иметь что-то в соответствии с гипотетическим примером ниже. В этом случае вместо использования файлов мы используем две переменные оболочки (Bash).

ПРИМЕР: (на основе вклада @Ed Morton)

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='\0' 'NR==FNR{str=$0; next} {print gsub(str,"")}' "$STRING" "$SUB_STRING"

Ответы [ 6 ]

0 голосов
/ 07 мая 2018

Обновление с учетом ваших комментариев ниже, если пробел одинаков в обеих строках:

awk 'BEGIN{print gsub(ARGV[2],"",ARGV[1])}' "$STRING" "$SUB_STRING"

или если пробел отличается от вашего примера, где строки STRING начинаются с 9 пробелов, а SUB_STRING - с 8:

$ awk 'BEGIN{gsub(/[[:space:]]+/,"[[:space:]]+",ARGV[2]); print gsub(ARGV[2],"",ARGV[1])}' "$STRING" "$SUB_STRING"

Оригинальный ответ:

С GNU awk, если ваш пробел совпадает между файлами и строкой поиска, не содержит метасхем RE, все, что вам нужно:

awk -v RS='^$' 'NR==FNR{str=$0; next} {print gsub(str,"")}' str file

или с любым awk, если ваш ввод также не содержит NUL-символов:

awk -v RS='\0' 'NR==FNR{str=$0; next} {print gsub(str,"")}' str file

, но для полного решения с объяснениями читайте:

С любым POSIX awk в любой оболочке на любом компьютере UNIX:

$ cat str
Bluetooth
        Soft blocked: no
        Hard blocked: no

$ awk '
NR==FNR { str=(str=="" ? "" : str ORS) $0; next }
{ rec=(rec=="" ? "" : rec ORS) $0 }
END {
    gsub(/[^[:space:]]/,"[&]",str) # make sure each non-space char is treated as literal
    gsub(/[[:space:]]+/,"[[:space:]]+",str) # make sure space differences do not matter
    print gsub(str,"",rec)
}
' str file
2

При использовании не POSIX awk, такого как nawk, просто используйте 0-9 вместо [:space:]. Если ваша строка поиска может содержать обратную косую черту, нам нужно добавить еще 1 gsub () для их обработки.

В качестве альтернативы, с GNU awk для нескольких символов RS:

$ awk -v RS='^$' 'NR==FNR{gsub(/[^[:space:]]/,"[&]"); gsub(/[[:space:]]+/,"[[:space:]]+"); str=$0; next} {print gsub(str,"")}' str file
2

или с любым awk, если ваш ввод не может содержать NUL-символов:

$ awk -v RS='\0' 'NR==FNR{gsub(/[^[:space:]]/,"[&]"); gsub(/[[:space:]]+/,"[[:space:]]+"); str=$0; next} {print gsub(str,"")}' str file
2

и так далее ...

0 голосов
/ 07 мая 2018

Другой awk

awk '
  NR==FNR{
    b[i++]=$0          # get each line of string in array b
    next}
  $0 ~ b[0]{            # if current record match first line of string
    for(j=1;j<i;j++){
      getline
      if($0!~b[j])  # next record do not match break
        j+=i}
     if(j==i)         # all record match string
       k++}
  END{
    print k}
' stringfile infile

РЕДАКТИРОВАТЬ:

И для XY задачи OP, простой скрипт:

cat scriptbash.sh

list="${1//$'\n'/@}"
var="${2//$'\n'/@}"
result="${list//$var}"
echo $(((${#list} - ${#result}) / ${#var}))

И вы называете это так:

. / Scriptbash.sh "$ String" "$ Sub_String"

0 голосов
/ 07 мая 2018

Это может работать для вас (GNU sed & wc):

sed -nr 'N;/^(\s*)Soft( blocked: no\s*)\n\1Hard\2$/P;D' file | wc -l

Вывести строку для каждого вхождения многострочного совпадения и сосчитать строки.

0 голосов
/ 07 мая 2018

Использовать Python:

#! /usr/bin/env python

import sys
import re

with open(sys.argv[1], 'r') as i:
    print(len(re.findall(sys.argv[2], i.read(), re.MULTILINE)))

вызвать как

$ ./search.py file.txt 'Bluetooth
 +Soft blocked: no
 +Hard blocked: no'

+ допускает один или несколько пробелов.

EDIT

Если содержимое уже находится в переменных bash, это еще проще

#! /usr/bin/env python

import sys
import re

print(len(re.findall(sys.argv[2], sys.argv[1], re.MULTILINE)))

вызвать как

$ ./search.py "$STRING" "$SUB_STRING"
0 голосов
/ 07 мая 2018

Вы можете попробовать это с GNU grep:

grep -zo -P ".*Bluetooth\n\s*Soft blocked: no\n\s*Hard blocked: no" <your_file> | grep -c "Bluetooth"

Первый grep будет соответствовать нескольким строкам и отображать только совпадающие группы. Подсчет вхождений Bluetooth по этому совпадению даст вам количество подходящих «подстрок».

Вывод первого grep:

1: asus-bluetooth: Bluetooth
         Soft blocked: no
         Hard blocked: no
113: hci0: Bluetooth
         Soft blocked: no
         Hard blocked: no

Вывод всей команды:

2
0 голосов
/ 07 мая 2018

Использование GNU awk:

$ awk '
BEGIN { RS="[0-9]+:" }      # number followed by colon is the record separator
NR==1 {                     # read the substring to b
    b=$0
    next
}
$0~b { c++ }                # if b matches current record, increment counter
END { print c }             # print counter value
' substringfile stringfile
2

Это решение требует, чтобы совпадение совпадало с объемом пространства, и ваш пример не работал бы как есть, так как подстрока имеет меньше места в отступе, чем строка. Обратите внимание, что из-за выбранного RS сопоставление, например, phy0: невозможно; в этом случае что-то вроде RS="(^|\n)[0-9]+:", вероятно, будет работать.

Другой:

$ awk '
BEGIN{ RS="^$" }                           # treat whole files as one record
NR==1 { b=$0; next }                       # buffer substringfile
{
    while(match($0,b)) {                   # count matches of b in stringfile
        $0=substr($0,RSTART+RLENGTH-1)
        c++
    }
}
END { print c }                            # output
' substringfile stringfile

Редактировать : Конечно, удалите раздел BEGIN и используйте подстановку процессов в Bash, как показано ниже:

$ awk '
NR==1 { 
    b=$0
    gsub(/^ +| +$/,"",b)                 # clean surrounding space from substring
    next 
}
{
    while(match($0,b)) {
        $0=substr($0,RSTART+RLENGTH-1)
        c++
    }
}
END { print c }
' <(echo $SUB_STRING) <(echo $STRING)    # feed it with process substitution
2

echo В процессе подстановки данные сглаживаются и удаляются дублирующиеся пробелы:

$ echo $SUB_STRING
Bluetooth Soft blocked: no Hard blocked: no

так что проблема с пространством должна немного облегчиться.

Edit2 : Основано на наблюдении @ EdMorton в ястребиных глазах в комментариях:

$ awk '
NR==1 { 
    b=$0
    gsub(/^ +| +$/,"",b)                 # clean surrounding space from substring
    next 
}
{ print gsub(b,"") }
' <(echo $SUB_STRING) <(echo $STRING)    # feed it with process substitution
2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...