打印包含特定单词的所有行正好k次

时间:2016-04-07 01:58:48

标签: linux shell

我必须从文件中搜索包含给定单词k次的所有行。我认为我应该使用grep / sed / awk,但我不知道如何。我的想法是使用sedgrep逐行检查每一行:

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

4 个答案:

答案 0 :(得分: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

答案 1 :(得分: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等中运行)

awk -v word="ab" -v num=3 '{for(i=1;i<=NF;i++){a[$i]++}} a[word]==num; {delete a}' input.txt

这可以通过构建一个关联数组来计算每一行上的单词,然后打印该行,如果&#34;有趣&#34;单词是num指定的内容。它与上面的bash脚本具有相同的基本概念,但是awk让我们做得更好。 :)

答案 2 :(得分: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

答案 3 :(得分:0)

也许您需要使用sed。如果您正在寻找字符序列,可以使用这样的代码。但是,它并没有区分单词本身和另一个单词中嵌入的单词(因此它将ababc视为包含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次出现(不会更多)并打印匹配的行。

如果你想要自己的话,那么你必须努力工作。您需要使用BSD(Mac OS X)上的-E选项或GNU -r 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字边界检测(.*?非贪婪匹配并不重要,因此更加简单对脚本的操作)。