2016-04-07 2 views
0

Мне нужно искать все строки из файла, которые содержат заданное слово ровно в k раз. Я думаю, что я должен использовать grep/sed/awk, но я не знаю, как это сделать. Моя идея состояла в том, чтобы проверить каждую строку за строкой, используя sed и grep вроде этого:Печать всех строк, содержащих определенное слово ровно в k раз

line=1 
while [ (sed -n -'($line)p' $name) -n ]; do 
    if [ (sed -n -'($line)p' $name | grep -w -c $word) -eq "$number" ]; then 
     sed -n -'($line)p' $name 
    fi 
    let line+=1 
done 

Моя первая проблема заключается в том, что я получаю следующее сообщение об ошибке: syntax error near unexpected token 'sed'. Затем я понимаю, что для моего тестового файла команда sed -n -'p1' test.txt | grep -w -c "ab" не возвращает точное число явлений «ab» в первой строке из моего файла (она возвращает 1, но есть 3 явления). Мой test.txt файл:

abc ab cds ab abcd edfs ab 
kkmd ab jnabc bad ab 
abcdefghijklmnop ab cdab ab ab 
abcde bad abc cdef a b 
+0

Если вы используете 'sed' или' awk', вам не нужно зацикливать файл. Это то, что эти языки делают в любом случае. – pfnuesel

+0

@pfnuesel я знаю, но я не знал другого способа проверить строку за строкой – Papanash

+0

Это то, что делает 'sed', проверяя линию за строкой. – pfnuesel

ответ

1

awk на помощь!

$ awk -F'\\<ab\\>' -v count=2 'NF==count+1' file 

kkmd ab jnabc bad ab 

отмечают, что \< и \> границы слов может быть gawk специфичны.

для присваивания значения переменной, я думаю, что проще всего будет

$ word=ab; awk -F"\\\<$word\\\>" -v count=2 'NF==count+1' file 

kkmd ab jnabc bad ab 
+0

Я представляю вашу команду в своем сценарии оболочки и не печатает эту строку – Papanash

+0

. Это тоже работает для меня. – pfnuesel

+0

@karakfa, как я могу заменить эту переменную на переменную '$ word'? – Papanash

0

Вы можете сделать это с помощью Grep

grep -E "(${word}.*){${number}}" test.txt 

Это выглядит для ${number} вхождений ${word} в каждой строке. Подстановочный знак .* необходим, так как мы также хотим сопоставлять случаи, когда совпадения ${word} не находятся рядом друг с другом.

Вот что я делаю:

$ echo 'abc ab cds ab abcd edfs ab 
kkmd ab jnabc bad ab 
abcdefghijklmnop ab cdab ab ab 
abcde bad abc cdef a b' > test.txt 

$ word=abc 
$ number=2 

$ grep -E "(${word}.*){${number}}" test.txt 
> abc ab cds ab abcd edfs ab 
> abcde bad abc cdef a b 
+0

Я пробовал с 'grep -E '($ {word}. *) {$ {Number})" ./$ name', и он не работает. – Papanash

+0

Что такое '$ name'? Вы должны добавить файл, который вы хотите «grep». См. Мой обновленный ответ. Если '$ name' - это имя вашего файла, можете ли вы более четко указать, что не работает? – pfnuesel

+0

$ name - это имя файла, это должно работать для любого текстового файла. И когда я сказал, что это не сработало, я хотел сказать, что он не печатал строки. – Papanash

1

Вы можете использовать Grep, но вы должны использовать его в два раза. (Вы не можете использовать один grep, потому что ERE не имеет возможности отрицать строку, вы можете только отменить выражение в скобках, которое будет соответствовать одиночным символам.)

Ниже приведено тестирование GNU grep v2.5.1, где вы можете использовать \< и \> как (возможно, непереносимых) разделители слов:

$ word="ab" 
$ < input.txt egrep "(\<$word\>.*){3}" | egrep -v "(\<$word\>.*){4}" 
abc ab cds ab abcd edfs ab 
abcdefghijklmnop ab cdab ab ab 
$ < input.txt egrep "(\<$word\>.*){2}" | egrep -v "(\<$word\>.*){3}" 
kkmd ab jnabc bad ab 

идея заключается в том, что мы извлечем из наших входных линий файлов с N вхождений слова, затем убирается из этого результата любой строк с N + 1 вхождениями. Конечно, строки с наименьшим количеством N не совпадают с первым grep.


Или, вы можете также сделать это в чистом Баш, если вы чувствуете себя немного мазохистом:

$ word="ab"; num=3 
$ readarray lines < input.txt 
$ for this in "${lines[@]}"; do declare -A words=(); x=($this); for y in "${x[@]}"; do ((words[$y]++)); done; [ "0${words[$word]}" -eq "$num" ] && echo "$this"; done 
abc ab cds ab abcd edfs ab 

abcdefghijklmnop ab cdab ab ab 

Разразившийся для облегчения чтения (или сценариев):

#!/usr/bin/env bash 

# Salt to taste 
word="ab"; num=3 

# Pull content into an array. This isn't strictly necessary, but I like 
# getting my file IO over with quickly if possible. 
readarray lines < input.txt 

# Walk through the array (or you could just walk through the input file) 
for this in "${lines[@]}"; do 

    # Initialize this line's counter array 
    declare -A words=() 

    # Break up the words into array elements 
    x=($this) 

    # Step though the array, counting each unique word 
    for y in "${x[@]}"; do 
    ((words[$y]++)) 
    done 

    # Check the count for "our" word 
    [ "0${words[$word]}" -eq $num ] && echo "$this" 

done 

Разве это не было весело? :)


Но это awk вариант имеет больше смысла для меня. Это портативный однострочный динамик, который не зависит от GNU awk (поэтому он будет работать в OS X, BSD и т. Д.)

Это работает путем создания ассоциативного массива для подсчета слов в каждой строке, а затем напечатать строку, если счетчик для «интересных» слов, что определяется как num. Это та же основная концепция, что и сценарий bash выше, но awk позволяет нам делать это намного лучше. :)

+0

Любовь получать downvotes без объяснения причин. – ghoti

+0

Не знаю, почему, но для первого примера это печать 'abcdefghijklmnop ab cdab ab ab'. Тот же вопрос здесь, как я могу изменить его для работы с переменными? – Papanash

+0

@ghoti Это выглядит как общая тема для ответов здесь - в настоящее время три ответа, все в -1 ... –

0

Возможно, вам нужно использовать sed. Если вы ищете последовательность символов, вы можете использовать такой код. Однако он не различает само слово и слово, встроенное в другое слово (поэтому он рассматривает ab и abc как оба содержит ab).

word="ab" 
number=2 

sed -n -e "/\($word.*\)\{$(($number + 1))\}/d" -e "/\($word.*\)\{$number\}/p" test.txt 
  • По умолчанию ничего не печатается (-n).
  • Первое выражение -e ищет 3 (или более) вхождения $word и удаляет строки, содержащие их (и переходит к следующей строке ввода). $(($number + 1)) - shell arithmetic.
  • Вторые выражения -e ищут 2 вхождения $word (их больше не будет) и печатает линии, которые соответствуют.

Если вы хотите слова сами по себе, вам придется работать намного сложнее. Вам потребуются расширенные регулярные выражения, запускаемые с опцией -E на BSD (Mac OS X) или -r с GNU sed.

number=2 
plus1=$(($number + 1)) 
word=ab 
sed -En -e "/(^|[^[:alnum:]])($word([^[:alnum:]]).*){$plus1}/d" \ 
     -e "/(^|[^[:alnum:]])($word([^[:alnum:]]).*){$number}$word$/d" \ 
     -e "/(^|[^[:alnum:]])($word([^[:alnum:]]|$).*){$number}/p" test.txt 

Это похоже на предыдущую версию, но имеет значительно более тонкую обработку слов.

  • Блок (^|[^[:alnum:]]) ищет либо в начале строки или не алфавитно-цифровой символ (изменить alnum к alpha в течение если вы не хотите, чтобы остановить цифры матчей).
  • Первый -e ищет начало строки или не буквенно-цифровой символ, за которым следует слово, а не буквенно-цифровое и ноль или несколько других символов, N + 1 раз, и удаляет такие строки (переход к следующей строке вход).
  • Второй -e ищет начало строки или не буквенно-цифровой символ, за которым следует слово, а не буквенно-цифровое и ноль или несколько других символов N раз, а затем слово снова следует за концом строки и удаляет такие линий.
  • Третий -e ищет начало строки или не буквенно-цифровой символ, за которым следует слово и не-буквенно-цифровое и ноль или более других символов N раз и печатает такие строки.

Учитывая (расширенный) входной файл:

abc NO ab cds ab abcd edfs ab 
kkmd YES ab jnabc bad ab 
abcd NO efghijklmnop ab cdab ab ab 
abcd NO efghijklmnop ab cdab ab ab 
abcd NO e bad abc cdef a b 
ab YES abcd abcd ab 
best YES ab ab candidly 
best YES ab ab candidly 
ab NO abcd abcd ab ab 
hope NO abcd abcd ab ab ab 
nope NO abcd abcd ab ab ab 
ab YES abcd abcd ab not bad 
said YES ab not so bad ab or bad 

Примера вывод:

kkmd YES ab jnabc bad ab 
ab YES abcd abcd ab 
best YES ab ab candidly 
best YES ab ab candidly 
ab YES abcd abcd ab not bad 
said YES ab not so bad ab or bad 

Это не тривиально упражнение в sed. Было бы проще, если бы вы могли полагаться на обнаружение границ слов.Например, в Perl:

number=2 
plus1=$(($number + 1)) 
word=ab 
perl -n -e "next if /(\b$word\b.*?){$plus1}/; 
      print if /(\b$word\b.*?){$number}/" test.txt 

Это производит один и тот же результат, что и sed сценария, но гораздо проще из-за обнаружение границы в \b слове (.*? нежадным соответствие не имеет решающее значение для работы сценарий).

Смежные вопросы