Check if all of multiple strings or regexes exist in a file
我想检查我的所有字符串是否都存在于一个文本文件中。它们可以存在于同一行或不同的行上。部分匹配应该可以。这样地:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ... string1 ... string2 ... string3 ... string1 string2 ... string1 string2 string3 ... string3 string1 string2 ... string2 string3 ... and so on |
在上面的例子中,我们可以用正则表达式代替字符串。
例如,以下代码检查文件中是否存在我的任何字符串:
1 2 3 | if grep -EFq"string1|string2|string3" file; then # there is at least one match fi |
如何检查它们是否都存在?因为我们只是对所有匹配的存在感兴趣,所以应该在所有字符串匹配后立即停止读取文件。
是否可以在不需要多次调用
另外,是否有一个字符串的解决方案可以很容易地扩展到regex?
awk是那些发明grep、shell等的人发明的用来做一般文本操作的工具,所以你不知道为什么要尝试避免它。
如果你想要的是简洁,下面是GNUawk一行程序,按照你的要求执行:
1 | awk 'NR==FNR{a[$0];next} {for(s in a) if(!index($0,s)) exit 1}' strings RS='^$' file |
以下是一系列其他信息和选项:
假设你真的在寻找字符串,它应该是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | awk -v strings='string1 string2 string3' ' BEGIN { numStrings = split(strings,tmp) for (i in tmp) strs[tmp[i]] } numStrings == 0 { exit } { for (str in strs) { if ( index($0,str) ) { delete strs[str] numStrings-- } } } END { exit (numStrings ? 1 : 0) } ' file |
一旦所有字符串匹配,上述操作将停止读取文件。
如果您要查找regexps而不是字符串,那么使用gnu awk来查找多字符rs,并在结尾部分保留$0,您可以执行以下操作:
1 | awk -v RS='^$' 'END{exit !(/regexp1/ && /regexp2/ && /regexp3/)}' file |
实际上,即使是字符串,也可以做到:
1 | awk -v RS='^$' 'END{exit !(index($0,"string1") && index($0,"string2") && index($0,"string3"))}' file |
上述2个GNUawk解决方案的主要问题是,像@anubhava的GNUgrep-p解决方案一样,整个文件必须一次读取到内存中,而对于上面的第一个awk脚本,它将在任何unix机器上任何shell中的任何awk中工作,并且一次只存储一行输入。
我看到你在问题下面添加了一条评论,说你可以有几千个"模式"。假设您的意思是"字符串",那么您可以从一个文件中读取它们,而不是将它们作为参数传递给脚本,例如,对于多字符RS,使用gnu awk,对于每行使用一个搜索字符串的文件:
1 2 3 4 5 6 7 8 | awk ' NR==FNR { strings[$0]; next } { for (string in strings) if ( !index($0,string) ) exit 1 } ' file_of_strings RS='^$' file_to_be_searched |
对于regexps,应该是:
1 2 3 4 5 6 7 8 | awk ' NR==FNR { regexps[$0]; next } { for (regexp in regexps) if ( $0 !~ regexp ) exit 1 } ' file_of_regexps RS='^$' file_to_be_searched |
如果您没有GNUawk,并且您的输入文件不包含nul字符,那么您可以使用
如果要搜索的文件太大,无法放入内存,那么字符串应该是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 | awk ' NR==FNR { strings[$0]; numStrings=NR; next } numStrings == 0 { exit } { for (string in strings) { if ( index($0,string) ) { delete strings[string] numStrings-- } } } END { exit (numStrings ? 1 : 0) } ' file_of_strings file_to_be_searched |
以及regexps的等效值:
1 2 3 4 5 6 7 8 9 10 11 12 13 | awk ' NR==FNR { regexps[$0]; numRegexps=NR; next } numRegexps == 0 { exit } { for (regexp in regexps) { if ( $0 ~ regexp ) { delete regexps[regexp] numRegexps-- } } } END { exit (numRegexps ? 1 : 0) } ' file_of_regexps file_to_be_searched |
下面是使用具有多个模式的
1 | git grep --all-match --no-index -l -e string1 -e string2 -e string3 file |
您还可以将模式与诸如
查看
--all-match When giving multiple pattern expressions, this flag is specified to limit the match to files that have lines to match all of them.
--no-index Search files in the current directory that is not managed by Git.
-l /--files-with-matches /--name-only Show only the names of files.
-e The next parameter is the pattern. Default is to use basic regexp.
其他需要考虑的参数:
--threads Number of grep worker threads to use.
-q /--quiet /--silent Do not output matched lines; exit with status 0 when there is a match.
要更改模式类型,还可以使用
首先,您可能想使用
这是基于以下假设:g
- 调用awk是不可接受的
- 多次调用
grep 是不可接受的。 - 任何其他外部工具的使用都是不可接受的。
- 调用
grep 少于一次是可以接受的 - 如果一切都找到了,它必须返回成功;如果没有,它必须返回失败。
- 使用
bash 代替外部工具是可以接受的。 - 对于正则表达式版本,
bash 版本大于等于3。
这可能满足您的所有要求:(regex版本缺少一些注释,请改为查看字符串版本)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | #!/bin/bash multimatch() { filename="$1" # Filename is first parameter shift # move it out of the way that"$@" is useful strings=("$@" ) # search strings into an array declare -a matches # Array to keep track which strings already match # Initiate array tracking what we have matches for for ((i=0;i<${#strings[@]};i++)); do matches[$i]=0 done while IFS= read -r line; do # Read file linewise foundmatch=0 # Flag to indicate whether this line matched anything for ((i=0;i<${#strings[@]};i++)); do # Loop through strings indexes if ["${matches[$i]}" -eq 0 ]; then # If no previous line matched this string yet string="${strings[$i]}" # fetch the string if [[ $line = *$string* ]]; then # check if it matches matches[$i]=1 # mark that we have found this foundmatch=1 # set the flag, we need to check whether we have something left fi fi done # If we found something, we need to check whether we # can stop looking if ["$foundmatch" -eq 1 ]; then somethingleft=0 # Flag to see if we still have unmatched strings for ((i=0;i<${#matches[@]};i++)); do if ["${matches[$i]}" -eq 0 ]; then somethingleft=1 # Something is still outstanding break # no need check whether more strings are outstanding fi done # If we didn't find anything unmatched, we have everything if ["$somethingleft" -eq 0 ]; then return 0; fi fi done <"$filename" # If we get here, we didn't have everything in the file return 1 } multimatch_regex() { filename="$1" # Filename is first parameter shift # move it out of the way that"$@" is useful regexes=("$@" ) # Regexes into an array declare -a matches # Array to keep track which regexes already match # Initiate array tracking what we have matches for for ((i=0;i<${#regexes[@]};i++)); do matches[$i]=0 done while IFS= read -r line; do # Read file linewise foundmatch=0 # Flag to indicate whether this line matched anything for ((i=0;i<${#strings[@]};i++)); do # Loop through strings indexes if ["${matches[$i]}" -eq 0 ]; then # If no previous line matched this string yet regex="${regexes[$i]}" # Get regex from array if [[ $line =~ $regex ]]; then # We use the bash regex operator here matches[$i]=1 # mark that we have found this foundmatch=1 # set the flag, we need to check whether we have something left fi fi done # If we found something, we need to check whether we # can stop looking if ["$foundmatch" -eq 1 ]; then somethingleft=0 # Flag to see if we still have unmatched strings for ((i=0;i<${#matches[@]};i++)); do if ["${matches[$i]}" -eq 0 ]; then somethingleft=1 # Something is still outstanding break # no need check whether more strings are outstanding fi done # If we didn't find anything unmatched, we have everything if ["$somethingleft" -eq 0 ]; then return 0; fi fi done <"$filename" # If we get here, we didn't have everything in the file return 1 } if multimatch"filename" string1 string2 string3; then echo"file has all strings" else echo"file miss one or more strings" fi if multimatch_regex"filename""regex1""regex2""regex3"; then echo"file match all regular expressions" else echo"file does not match all regular expressions" fi |
基准点
我在Linux 4.16.2的arch/arm/中搜索了
结果:(用
- 江东十一〔13〕49号
- 埃多克斯1〔14〕55秒
- MatcALL:105s
- 文件匹配所有名称:4s
- awk(第一版):4s
- AGRP:4.5S
- Perl Re(-r):10.5秒
- Perl非RE:9.5秒
- Perl非重新优化:5s(删除了getopt::std和regex支持以加快启动速度)
- Perl重新优化:7s(删除了getopt::std和non-regex对更快启动的支持)
- Git GRIP: 3.5S
- C版(无regex):1.5s
(多次调用
此
1 2 3 4 5 6 7 8 9 10 11 12 | cat fileSearch.awk re =="" { exit } { split($0, null,"\\<(" re"\\>)", b) for (i=1; i<=length(b); i++) gsub("\\<" b[i]"([|]|$)","", re) } END { exit (re !="") } |
然后使用它作为:
1 2 3 4 5 | if awk -v re='string1|string2|string3' -f fileSearch.awk file; then echo"all strings were found" else echo"all strings were not found" fi |
或者,您可以将此
1 | grep -qzP '(?s)(?=.*\bstring1\b)(?=.*\bstring2\b)(?=.*\bstring3\b)' file |
- 使用
-z 我们使grep 把完整的文件读入一个字符串。 - 我们使用多个lookahead断言断言文件中存在所有字符串。
- regex必须使用
(?s) 或DOTALL mod使.* 在线路上匹配。
根据
1 2 3 | -z, --null-data Treat input and output data as sequences of lines, each terminated by a zero byte (the ASCII NUL character) instead of a newline. |
递归解决方案。逐个迭代文件。对于每个文件,检查它是否匹配第一个模式并提前中断(第一个匹配时为-m1),仅当它匹配第一个模式时,搜索第二个模式,依此类推:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #!/bin/bash patterns="$@" fileMatchesAllNames () { file=$1 if [[ $# -eq 1 ]] then echo"$file" else shift pattern=$1 shift grep -m1 -q"$pattern""$file" && fileMatchesAllNames"$file" $@ fi } for file in * do test -f"$file" && fileMatchesAllNames"$file" $patterns done |
用途:
1 2 | ./allfilter.sh cat filter java test.sh |
在当前DIR中搜索令牌"CAT"、"过滤器"和"Java"。只在"test.sh"中找到它们。
因此,grep通常在最坏的情况下被调用(在每个文件的最后一行中查找第一个n-1模式,n-th模式除外)。
但是,如果可能的话,有了一个通知性的排序(很少先匹配,早期先匹配),解决方案应该是合理的快速,因为许多文件因为不匹配第一个关键字而被提前放弃,或者因为它们匹配了接近顶部的关键字而被提前接受。
示例:搜索一个scala源文件,该文件包含tailrec(很少使用)、mutable(很少使用,但如果使用,则在import语句中接近top)、main(很少使用,通常不接近top)和println(经常使用,位置不可预测),您将对其进行排序:
1 | ./allfilter.sh mutable tailrec main println |
性能:
1 2 | ls *.scala | wc 89 89 2030 |
在89个scala文件中,我有关键词分布:
1 2 3 4 5 | for keyword in mutable tailrec main println; do grep -m 1 $keyword *.scala | wc -l ; done 16 34 41 71 |
使用稍微修改过的脚本版本搜索它们,允许使用filepattern作为第一个参数,大约需要0.2秒:
1 2 3 4 5 6 7 8 9 10 11 | time ./allfilter.sh"*.scala" mutable tailrec main println Filepattern: *.scala Patterns: mutable tailrec main println aoc21-2017-12-22_00:16:21.scala aoc25.scala CondenseString.scala Partition.scala StringCondense.scala real 0m0.216s user 0m0.024s sys 0m0.028s |
在接近15000条代码行中:
1 2 | cat *.scala | wc 14913 81614 610893 |
更新:
在阅读了对这个问题的评论之后,我们可能会谈论上千个模式,把它们作为参数来处理似乎不是一个聪明的主意;最好从文件中读取它们,并将文件名作为参数传递——也许文件列表也要过滤:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #!/bin/bash filelist="$1" patternfile="$2" patterns="$(< $patternfile)" fileMatchesAllNames () { file=$1 if [[ $# -eq 1 ]] then echo"$file" else shift pattern=$1 shift grep -m1 -q"$pattern""$file" && fileMatchesAllNames"$file" $@ fi } echo -e"Filepattern: $filepattern\tPatterns: $patterns" for file in $(< $filelist) do test -f"$file" && fileMatchesAllNames"$file" $patterns done |
如果模式/文件的数量和长度超过了参数传递的可能性,则模式列表可以拆分为多个模式文件并在循环中处理(例如20个模式文件):
1 2 3 4 | for i in {1..20} do ./allfilter2.sh file.$i.lst pattern.$i.lst > file.$((i+1)).lst done |
你可以
利用
grep 的-o --only-matching 选项(强制只输出匹配线的匹配部分,每个部分都在单独的输出线上)。然后用
sort -u 消除匹配字符串的重复出现,最后检查剩余行的计数是否等于输入字符串的计数。
论证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | $ cat input ... string1 ... string2 ... string3 ... string1 string2 ... string1 string2 string3 ... string3 string1 string2 ... string2 string3 ... and so on $ grep -o -F $'string1 string2 string3' input|sort -u|wc -l 3 $ grep -o -F $'string1 string3' input|sort -u|wc -l 2 $ grep -o -F $'string1 string2 foo' input|sort -u|wc -l 2 |
这个解决方案的一个缺点(不满足部分匹配应该是正常的要求)是
1 2 3 4 5 6 7 | $ grep -o -F $'abc bcd' <<< abcd abc $ grep -o -F $'bcd abc' <<< abcd abc |
请注意,此方法/解决方案仅适用于固定字符串。它不能扩展到regex,因为一个regex可以匹配多个不同的字符串,并且我们不能跟踪哪个匹配对应于哪个regex。最好是将匹配项存储在一个临时文件中,然后使用一次一个regex多次运行
作为bash脚本实现的解决方案:
MatCHALAL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | #!/usr/bin/env bash if [ $# -lt 2 ] then echo"Usage: $(basename"$0") input_file string1 [string2 ...]" exit 1 fi function find_all_matches() ( infile="$1" shift IFS=$' ' newline_separated_list_of_strings="$*" grep -o -F"$newline_separated_list_of_strings""$infile" ) string_count=$(($# - 1)) matched_string_count=$(find_all_matches"$@"|sort -u|wc -l) if ["$matched_string_count" -eq"$string_count" ] then echo"ALL strings matched" exit 0 else echo"Some strings DID NOT match" exit 1 fi |
论证:
1 2 3 4 5 6 7 8 9 10 11 | $ ./matchall Usage: matchall input_file string1 [string2 ...] $ ./matchall input string1 string2 string3 ALL strings matched $ ./matchall input string1 string2 ALL strings matched $ ./matchall input string1 string2 foo Some strings DID NOT match |
检查文件是否都有三个模式的最简单方法是只获取匹配的模式,只输出唯一的部分和计数行。然后您可以使用一个简单的测试条件来检查它:
1 | grep_lines=$(grep -Eo 'string1|string2|string3' file | uniq | wc -l) |
关于您的第二个问题,我认为一旦找到多个模式,就不可能停止读取文件。我已经阅读了grep的手册页,没有任何可以帮助你的选择。您只能使用选项
很确定为此需要一个自定义函数。
这是一个有趣的问题,在grep的手册页中没有任何明显的地方可以给出一个简单的答案。可能有一个疯狂的regex可以做到这一点,但通过一个简单的greps链可能会更清晰,即使这最终会扫描文件n次。至少-q选项在每次第一次匹配时都会使其保释,如果找不到其中一个字符串,则&;将快捷计算。
1 2 3 4 5 6 7 | $grep -Fq string1 t && grep -Fq string2 t && grep -Fq string3 t $echo $? 0 $grep -Fq string1 t && grep -Fq blah t && grep -Fq string3 t $echo $? 1 |
1 | perl -lne '%m = (%m, map {$_ => 1} m!\b(string1|string2|string3)\b!g); END { print scalar keys %m == 3 ?"Match":"No Match"}' file |
忽略"有没有可能不…或者使用像
(为您的系统使用适当的shebang或类似于
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | #!/usr/bin/perl use Getopt::Std; # option parsing my %opts; my $filename; my @patterns; getopts('rf:',\%opts); # Allowing -f <filename> and -r to enable regex processing if ($opts{'f'}) { # if -f is given $filename = $opts{'f'}; @patterns = @ARGV[0 .. $#ARGV]; # Use everything else as patterns } else { # Otherwise $filename = $ARGV[0]; # First parameter is filename @patterns = @ARGV[1 .. $#ARGV]; # Rest is patterns } my $use_re= $opts{'r'}; # Flag on whether patterns are regex or not open(INF,'<',$filename) or die("Can't open input file '$filename'"); while (my $line = <INF>) { my @removal_list = (); # List of stuff that matched that we don't want to check again for (my $i=0;$i <= $#patterns;$i++) { my $pattern = $patterns[$i]; if (($use_re&& $line =~ /$pattern/) || # regex match (!$use_re&& index($line,$pattern) >= 0)) { # or string search push(@removal_list,$i); # Mark to be removed } } # Now remove everything we found this time # We need to work backwards to keep us from messing # with the list while we're busy for (my $i=$#removal_list;$i >= 0;$i--) { splice(@patterns,$removal_list[$i],1); } if (scalar(@patterns) == 0) { # If we don't need to match anything anymore close(INF) or warn("Error closing '$filename'"); exit(0); # We found everything } } # End of file close(INF) or die("Error closing '$filename'"); exit(1); # If we reach this, we haven't matched everything |
保存为
1 | ./matcher filename string1 string2 string3 'complex string' |
这将搜索正则表达式:
1 | ./matcher -r filename regex1 'regex2' 'regex4' |
(文件名可以用
1 | ./matcher -f filename -r string1 string2 string3 'complex string' |
它仅限于单行匹配模式(由于处理文件线条)。
从shell脚本调用大量文件时,性能比
无论是否使用正则表达式,都可以通过硬编码显著加快速度,代价是灵活性。(请看我的基准,看看删除
也许和GNU SED一起
猫匹配词
1 2 3 4 5 6 7 8 9 10 11 12 | sed -z ' /\b'"$2"'/!bA /\b'"$3"'/!bA /\b'"$4"'/!bA /\b'"$5"'/!bA s/.*/0 / q :A s/.*/1 / '"$1" |
你这样称呼它:
1 | ./match_word.sh infile string1 string2 string3 |
如果找到所有匹配项,则返回0,否则返回1
在这里你可以找4根弦
如果您需要更多,可以添加类似
1 | /\b'"$x"'/!bA |
为了"解决方案的完整性",您可以使用不同的工具,避免使用多个grep和awk/sed或大(可能是慢)shell循环;这样的工具是agrep。
像
一些测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | $ cat file6 str4 str1 str2 str3 str1 str2 str1 str2 str3 str3 str1 str2 str2 str3 $ agrep -d '$$ ' 'str3;str2;str1;str4' file6;echo $? str4 str1 str2 str3 str1 str2 str1 str2 str3 str3 str1 str2 str2 str3 0 $ agrep -d '$$ ' 'str3;str2;str1;str4;str5' file6;echo $? 1 $ agrep -p 'str3;str2;str1' file6 #-p prints lines containing all three patterns in any position str1 str2 str3 str3 str1 str2 |
没有一个工具是完美的,
还有一个Perl变量-只要所有给定的字符串都匹配..即使文件是半读的,处理过程也会完成并只打印结果
1 2 3 4 | > perl -lne ' /\b(string1|string2|string3)\b/ and $m{$1}++; eof if keys %m == 3; END { print keys %m == 3 ?"Match":"No Match"}' all_match.txt Match > perl -lne ' /\b(string1|string2|stringx)\b/ and $m{$1}++; eof if keys %m == 3; END { print keys %m == 3 ?"Match":"No Match"}' all_match.txt No Match |
假设您要检查的所有字符串都在一个文件strings.txt中,并且您要签入的文件是input.txt,那么下面的一行程序将执行以下操作:
根据评论更新了答案:
1 | $ diff <( sort -u strings.txt ) <( grep -o -f strings.txt input.txt | sort -u ) |
说明:
使用grep的-o选项只匹配您感兴趣的字符串。这将给出文件input.txt中存在的所有字符串。然后使用diff获取未找到的字符串。如果找到所有字符串,结果将为零。或者,只需检查diff的退出代码。
它没有做什么:
- 找到所有匹配项后立即退出。
- 可扩展到regx。
- 重叠的匹配项。
它的作用是:
- 查找所有匹配项。
- 给grep打一个电话。
- 不使用awk或python。
在python中,使用fileinput模块可以在命令行或从stdin逐行读取文本中指定文件。您可以将字符串硬编码为一个python列表。
1 2 3 4 5 6 7 | # Strings to match, must be valid regular expression patterns # or be escaped when compiled into regex below. strings = ( r'string1', r'string2', r'string3', ) |
或者从另一个文件读取字符串
1 2 3 4 5 6 7 8 9 10 11 12 13 | import re from fileinput import input, filename, nextfile, isfirstline for line in input(): if isfirstline(): regexs = map(re.compile, strings) # new file, reload all strings # keep only strings that have not been seen in this file regexs = [rx for rx in regexs if not rx.match(line)] if not regexs: # found all strings print filename() nextfile() |
这些答案中的许多都是正确的。
但是,如果性能是一个问题——如果输入量很大,并且有成千上万个模式,那么使用像
有限自动机将对每个输入字符执行一些机器指令,而不考虑模式的数量。
一个无装饰的柔性解决方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | %{ void match(int); %} %option noyywrap %% "abc" match(0); "ABC" match(1); [0-9]+ match(2); /* Continue adding regex and exact string patterns... */ [ \t ] /* Do nothing with whitespace. */ . /* Do nothing with unknown characters. */ %% // Total number of patterns. #define N_PATTERNS 3 int n_matches = 0; int counts[10000]; void match(int n) { if (counts[n]++ == 0 && ++n_matches == N_PATTERNS) { printf("All matched! "); exit(0); } } int main(void) { yyin = stdin; yylex(); printf("Only matched %d patterns. ", n_matches); return 1; } |
不利的一面是,您必须为每一组给定的模式构建这个模型。这还不错:
1 2 | flex matcher.y gcc -O lex.yy.c -o matcher |
现在运行它:
1 | ./matcher < input.txt |
对于普通速度,没有外部工具限制,也没有regex,这个(粗糙的)C版本做得很好。(可能仅限于Linux,尽管它应该在所有与
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | #include <sys/mman.h> #include <sys/stat.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> /* https://stackoverflow.com/a/8584708/1837991 */ inline char *sstrstr(char *haystack, char *needle, size_t length) { size_t needle_length = strlen(needle); size_t i; for (i = 0; i < length; i++) { if (i + needle_length > length) { return NULL; } if (strncmp(&haystack[i], needle, needle_length) == 0) { return &haystack[i]; } } return NULL; } int matcher(char * filename, char ** strings, unsigned int str_count) { int fd; struct stat sb; char *addr; unsigned int i = 0; /* Used to keep us from running of the end of strings into SIGSEGV */ fd = open(filename, O_RDONLY); if (fd == -1) { fprintf(stderr,"Error '%s' with open on '%s' ",strerror(errno),filename); return 2; } if (fstat(fd, &sb) == -1) { /* To obtain file size */ fprintf(stderr,"Error '%s' with fstat on '%s' ",strerror(errno),filename); close(fd); return 2; } if (sb.st_size <= 0) { /* zero byte file */ close(fd); return 1; /* 0 byte files don't match anything */ } /* mmap the file. */ addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); if (addr == MAP_FAILED) { fprintf(stderr,"Error '%s' with mmap on '%s' ",strerror(errno),filename); close(fd); return 2; } while (i++ < str_count) { char * found = sstrstr(addr,strings[0],sb.st_size); if (found == NULL) { /* If we haven't found this string, we can't find all of them */ munmap(addr, sb.st_size); close(fd); return 1; /* so give the user an error */ } strings++; } munmap(addr, sb.st_size); close(fd); return 0; /* if we get here, we found everything */ } int main(int argc, char *argv[]) { char *filename; char **strings; unsigned int str_count; if (argc < 3) { /* Lets count parameters at least... */ fprintf(stderr,"%i is not enough parameters! ",argc); return 2; } filename = argv[1]; /* First parameter is filename */ strings = argv + 2; /* Search strings start from 3rd parameter */ str_count = argc - 2; /* strings are two ($0 and filename) less than argc */ return matcher(filename,strings,str_count); } |
编译时使用:
1 | gcc matcher.c -o matcher |
与它一起运行:
1 | ./matcher filename needle1 needle2 needle3 |
信用:
- 使用SSTRSTR
- 文件处理大多是从
mmap 手册页上被盗的
笔记:
- 它将对匹配字符串之前的文件部分进行多次扫描,但只打开文件一次。
- 整个文件可能最终加载到内存中,特别是如果字符串不匹配,操作系统需要决定
- 可以通过使用posix regex库添加regex支持(性能可能比grep稍好一点-它应该基于同一个库,并且只打开一次文件搜索多个regex会减少开销)
- 包含空值的文件应该可以工作,但不能用空值搜索字符串…
- 除NULL以外的所有字符都应可搜索(
、等)
下面的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | #!/usr/bin/env python import re # the file to search filename = '/path/to/your/file.txt' # list of patterns -- can be read from a file or command line # depending on the count patterns = [r'py.*$', r'\s+open\s+', r'^import\s+'] patterns = map(re.compile, patterns) with open(filename) as f: for line in f: # search for pattern matches results = map(lambda x: x.search(line), patterns) # remove the patterns that did match results = zip(results, patterns) results = filter(lambda x: x[0] == None, results) patterns = map(lambda x: x[1], results) # stop if no more patterns are left if len(patterns) == 0: break # print the patterns which were not found for p in patterns: print p.pattern |
如果您处理的是普通(非regex)字符串,则可以添加对普通字符串(
那能解决你的问题吗?
1 2 | $ cat allstringsfile | tr ' ' ' ' | awk -f awkpattern1 |
其中allStringsFile是文本文件,如原始问题中所示。awkpattern1包含字符串模式,条件为:
1 2 | $ cat awkpattern1 /string1/ && /string2/ && /string3/ |
我在答案中没有看到一个简单的计数器,所以这里有一个反方向的解决方案,它使用
1 2 3 4 5 6 7 8 9 | /string1/ { a = 1 } /string2/ { b = 1 } /string3/ { c = 1 } { if (c + a + b == 3) { print"Found!"; exit; } } |
通用脚本
要通过shell参数扩展用法,请执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #! /bin/sh awk -v vars="$*" -v argc=$# ' BEGIN { split(vars, args); } { for (arg in args) { if (!temp[arg] && $0 ~ args[arg]) { inc++; temp[arg] = 1; } } if (inc == argc) { print"Found!"; exit; } } END { exit 1; } ' filename |
用法(可以在其中传递正则表达式):
1 | ./script"str1?""(wo)?men" str3 |
或者应用一系列模式:
1 | ./script"str1? (wo)?men str3" |