检查文件中是不是存在所有多个字符串或正则表达式

Posted

技术标签:

【中文标题】检查文件中是不是存在所有多个字符串或正则表达式【英文标题】:Check if all of multiple strings or regexes exist in a file检查文件中是否存在所有多个字符串或正则表达式 【发布时间】:2018-09-20 14:42:25 【问题描述】:

我想检查我的 all 字符串是否存在于文本文件中。它们可以存在于同一行或不同行。部分匹配应该没问题。像这样:

...
string1
...
string2
...
string3
...
string1 string2
...
string1 string2 string3
...
string3 string1 string2
...
string2 string3
... and so on

在上面的例子中,我们可以用正则表达式代替字符串。

例如,以下code 检查文件中是否存在任何我的字符串:

if grep -EFq "string1|string2|string3" file; then
  # there is at least one match
fi

如何检查所有是否存在?由于我们只对所有匹配项的存在感兴趣,因此我们应该在所有字符串匹配后立即停止读取文件。

是否可以在不必多次调用grep 的情况下执行此操作(当输入文件很大或我们有大量字符串要匹配时,它不会缩放)或使用awk 之类的工具或python?

另外,是否有可以轻松扩展正则表达式的字符串解决方案?

【问题讨论】:

重复问题:unix.stackexchange.com/questions/55359/… @IanMcGowan:这不是骗子。那篇文章讨论了在同一行同时发生的所有模式。我的兴趣是在同一行或不同行上找到它们。 啊,好点,我的坏。我应该加一个问号。 @codeforester:哇,几千,这似乎是一个重要的信息。对于如此大的数字,通过 shell 将它们作为参数传递可能会出现问题。也许可以将它们分成数百个参数的块,过滤一个文件列表并从第一次调用中获取一个过滤后的文件列表,生成一个简化的文件列表作为下一个块的第二次调用的输入,依此类推。除了将文件和模式作为参数传递之外,人们还可以传递包含这些文件和模式的文件名,只需少量修改,我的脚本就可以做到这一点,但其他脚本也可以。 如果它要求在没有它们的情况下进行操作,为什么会有awkpython 标签? 【参考方案1】:

先删除行分隔符,再正常使用grep多次,模式数如下。

例子:让文件内容如下

PAT1
PAT2
PAT3
something
somethingelse

cat file | tr -d "\n" | grep "PAT1" | grep "PAT2" | grep -c "PAT3"

【讨论】:

【参考方案2】:

另一个 Perl 变体 - 只要所有给定的字符串都匹配..即使文件被读到一半,处理完成并打印结果

> 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

【讨论】:

【参考方案3】:

git grep

下面是使用git grep 和多种模式的语法:

git grep --all-match --no-index -l -e string1 -e string2 -e string3 file

您还可以将模式与 Boolean 表达式组合,例如 --and--or--not

查看man git-grep 寻求帮助。


--all-match 给出多个模式表达式时,指定此标志以将匹配限制为具有匹配所有行的文件

--no-index 在当前目录中搜索非 Git 管理的文件。

-l/--files-with-matches/--name-only 只显示文件名。

-e 下一个参数是模式。默认是使用基本的正则表达式。

要考虑的其他参数:

--threads 要使用的 grep 工作线程数。

-q/--quiet/--silent不输出匹配行;匹配时退出状态为 0。

要更改模式类型,您还可以使用-G/--basic-regexp(默认)、-F/--fixed-strings-E/--extended-regexp-P/--perl-regexp、@987654349 @ 等。

【讨论】:

这会报告每个文件的多个匹配项。但是对于time git grep --name-only --all-match -e ...,它只报告匹配的文件,而且速度非常快——我的解决方案对于我的测试用例大约需要 0.2 秒,而你的解决方案需要 0.02 秒。选项“-f patternfile”也可能很有趣。最重要的可能是:它简单、清晰、紧凑,而且随着时间的推移肯定会比我自己开发的脚本获得更多的支持。 这就是 SO 如此出色的原因。每个人处理问题的方式不同,有时会出现意想不到的情况。 我能说的最好的git grep -q --all-match --no-index -l -F -f strings.txt file 似乎只要您不介意安装 git 工具就可以正常工作。它是一次将整个文件读入内存还是一次在内存中存储 1 行?一次可以搜索多少个字符串有限制吗? @EdMorton 没有字符串限制,只是需要更长的时间。内存管理方面,我不确定,可以查看source code of grep.c(查找all_match)。但总的来说,它的设计目的是快速。 谢谢,从读取源文件中我可以看出它总是将整个输入文件读入内存。【参考方案4】:
$ cat allstringsfile | tr '\n' ' ' |  awk -f awkpattern1

allstringsfile 是您的文本文件,与原始问题一样。 awkpattern1 包含字符串模式,带有 && 条件:

$ cat awkpattern1
/string1/ && /string2/ && /string3/

【讨论】:

这对大文件来说是最佳选择吗?【参考方案5】:

下面的python 脚本应该可以解决问题。它确实为每一行多次调用grep (re.search) 的等价物——即它为每一行搜索每个模式,但由于你不是每次都分叉一个进程,它应该更多高效的。此外,它会删除已经找到的模式,并在找到所有模式后停止。

#!/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

如果您处理的是纯(非正则表达式)字符串,您可以为纯字符串 (string in line) 添加单独的检查 - 效率会稍高一些。

这能解决你的问题吗?

【讨论】:

【参考方案6】:

首先,您可能想使用awk。由于您在问题陈述中消除了该选项,是的,可以这样做,这提供了一种方法。它可能比使用awk 慢得多,但如果你想这样做......

这是基于以下假设:G

调用 AWK 是不可接受的 多次调用grep 是不可接受的 不得使用任何其他外部工具 调用grep 少于一次是可以接受的 如果全部找到则返回成功,否则返回失败 可以使用bash 代替外部工具 bash version is >= 3 for the regular expression version

这可能满足您的所有要求:(正则表达式版本缺少一些 cmets,请查看字符串版本)

#!/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/ 中针对字符串“void”、“function”和“#define”进行了一些基准测试搜索 .c.h.sh。 (添加了外壳包装器/调整的代码都可以称为testname &lt;filename&gt; &lt;searchstring&gt; [...],并且可以使用if 来检查结果)

结果:(用timereal 时间四舍五入到最接近的半秒测量)

multimatch:49秒 multimatch_regex:55秒 matchall:10.5s fileMatchesAllNames:4s awk(第一版):4s 同意:4.5 秒 Perl re (-r): 10.5s Perl non-re:9.5s Perl non-re optimised: 5s(移除 Getopt::Std 和正则表达式支持以加快启动速度) Perl re optimised: 7s(删除 Getopt::Std 和非正则表达式支持以加快启动速度) git grep:3.5s C version(无正则表达式):1.5s

(多次调用grep,尤其是使用递归方法,效果比我预期的要好)

【讨论】:

一些基准测试(比如在 scala 文件示例中)会很有趣......它可能比 AWK 慢得多 - 这主要是为了证明可以满足规定的要求......(没有提到速度作为要求)(这似乎也没有使用外部进程 - 这可能有利于速度,但是 bash 文本处理可能不利于速度,至少与 grep 中的 C 代码相比......) 是的,你是对的,它会比 awk 脚本慢几个数量级。见why-is-using-a-shell-loop-to-process-text-considered-bad-practice。您在脚本中的多个位置使用了 pattern 这个词 - 这是一个高度模棱两可的术语,因此通常应该避免使用它,所以如果您在谈论 stringregexp、a globbing pattern 或其他。 @EdMorton:这似乎是避免标准工具(如 AWK)(或非标准工具,如 Perl)的唯一方法(我正在假设唯一可接受的非shell 工具是grep,只调用一次...)(问题是问它是否“可能”,而不是一个好主意;-))“模式”用于搜索字符串(可以是正则表达式或与通配符匹配的纯字符串,具体取决于变体)(正则表达式版本是在我意识到 bash 具有内置正则表达式后完成的,并且是对第一个版本的简单修改)(详细的 cmets 在 fork 之后添加) 感谢您进行基准测试。由于 OP 正在搜索数千个字符串,您是否可以使用大量字符串重试(例如,至少 1,000 个,所有这些字符串都出现在目标文件中,其中一些是彼此的子集,其中一些包含正则表达式元字符)?随着要搜索的字符串数量变大(并且匹配),各种解决方案的执行方式之间存在巨大差异,加上某些解决方案将失败,因为字符串是其他字符串的子字符串或包含 RE 字符的字符串,而这些差异不会只显示这 3 个字符串 @EdMorton:这变得很棘手——许多解决方案都有不同的接口,映射起来变得很棘手(而且可能很慢)。 CLI 可能需要一个非 shell 方法来调用它们来构建一个接近 ARG_MAXargv (尽管如果它们具有适当的退出代码和 &amp;&amp; 运算符,则可以任意组合它们,缺点是多次扫描如果第一部分匹配,则文件)【参考方案7】:

对于简单的速度,没有外部工具限制,也没有正则表达式,这个(粗略的)C 版本做得不错。 (可能仅适用于 Linux,尽管它应该适用于所有带有mmap 的类 Unix 系统)

#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://***.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'\n",strerror(errno),filename);
        return 2;
    

    if (fstat(fd, &sb) == -1)           /* To obtain file size */
        fprintf(stderr,"Error '%s' with fstat on '%s'\n",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'\n",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!\n",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);

编译:

gcc matcher.c -o matcher

运行它:

./matcher filename needle1 needle2 needle3

学分:

使用sstrstr 文件处理大部分来自mmapman page

注意事项:

它将多次扫描匹配字符串之前的文件部分 - 但它只会打开文件一次。 整个文件最终可能会加载到内存中,特别是如果字符串不匹配,操作系统需要决定 可以通过使用POSIX regex library 添加正则表达式支持(性能可能比 grep 稍好 - 它应该基于相同的库,并且您可以通过只打开一次文件来搜索多个正则表达式) 包含空值的文件应该可以工作,但不能用它们搜索字符串... 除了 null 之外的所有字符都应该是可搜索的(\r、\n 等)

【讨论】:

【参考方案8】:

Awk 是发明 grep、shell 等的人发明的工具,用于执行此类一般文本操作工作,因此不知道您为什么要避免使用它。

如果您想要简洁,这里是 GNU awk 单行代码,可以满足您的要求:

awk 'NR==FNRa[$0];next for(s in a) if(!index($0,s)) exit 1' strings RS='^$' file

还有很多其他信息和选项:

假设你真的在寻找字符串,那就是:

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

一旦所有字符串都匹配,上述将停止读取文件。

如果您正在寻找正则表达式而不是字符串,那么使用 GNU awk 进行多字符 RS 并在 END 部分保留 $0,您可以这样做:

awk -v RS='^$' 'ENDexit !(/regexp1/ && /regexp2/ && /regexp3/)' file

其实,即使是字符串你也可以这样做:

awk -v RS='^$' 'ENDexit !(index($0,"string1") && index($0,"string2") && index($0,"string3"))' file

上述 2 个 GNU awk 解决方案的主要问题是,就像@anubhava 的 GNU grep -P 解决方案一样,必须一次将整个文件读入内存,而使用上面的第一个 awk 脚本,它可以工作任何 UNIX 机器上的任何 shell 中的任何 awk,一次只存储一行输入。

我看到你在你的问题下添加了一条评论,说你可能有几千个“模式”。假设您的意思是“字符串”,那么您可以从文件中读取它们,而不是将它们作为参数传递给脚本,例如使用 GNU awk 进行多字符 RS 和每行一个搜索字符串的文件:

awk '
NR==FNR  strings[$0]; next 

    for (string in strings)
        if ( !index($0,string) )
            exit 1

' file_of_strings RS='^$' file_to_be_searched

对于正则表达式,它是:

awk '
NR==FNR  regexps[$0]; next 

    for (regexp in regexps)
        if ( $0 !~ regexp )
            exit 1

' file_of_regexps RS='^$' file_to_be_searched

如果您没有 GNU awk 并且您的输入文件不包含 NUL 字符,那么您可以通过使用 RS='\0' 而不是 RS='^$' 或一次附加到变量一行来获得与上述相同的效果它被读取,然后在 END 部分处理该变量。

如果您的 file_to_be_searched 太大而无法放入内存,那么对于字符串来说就是这样:

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

和正则表达式的等价物:

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

【讨论】:

这是一个很好的答案。开始赏金以吸引更多关注。 好的,但是您确实应该澄清是否需要字符串或正则表达式的解决方案,以及您是否需要“全字”匹配或部分匹配。 好。认真思考每个解决方案在做什么,因为您发布的示例输入并没有涵盖它需要证明解决方案是否有效的所有情况,而所有解决方案都会在给定示例输入的情况下产生预期的输出,大多数给定不同的输入,它们将失败。因此,要么花点时间找出涵盖所有需求的不同示例输入(特别是不应该匹配但难以处理的特定文件),要么真正考虑每个解决方案的逻辑,以确保它确实解决了 all 您的需求。 哦,如果您正在考虑任何涉及 shell 循环的解决方案,请确保您理解 why-is-using-a-shell-loop-to-process-text-considered-bad-practice,并考虑给定的解决方案将在内存和执行速度方面对大文件执行什么操作. @EdMorton 非常好的答案!我只缺少一件事,如果搜索的字符串被换行符分割怎么办。想象一本教科书,您要搜索的字符串被换行符分开。这有点困难,但它可能是这个答案的一个很好的补充!【参考方案9】:

其中许多答案都很好。

但是,如果性能是一个问题——如果输入很大并且你有成千上万的模式当然有可能——那么你将获得 large 加速使用像 lexflex 这样的工具,它生成一个真正的确定性有限自动机作为识别器,而不是每个模式调用一次正则表达式解释器。

有限自动机将对每个输入字符执行几条机器指令无论模式数量如何

简洁的弹性解决方案:

%
void match(int);
%
%option noyywrap

%%

"abc"       match(0);
"ABC"       match(1);
[0-9]+      match(2);
/* Continue adding regex and exact string patterns... */

[ \t\n]     /* 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!\n");
    exit(0);
  


int main(void) 
  yyin = stdin;
  yylex();
  printf("Only matched %d patterns.\n", n_matches);
  return 1;

缺点是您必须为每组给定的模式构建它。这还不错:

flex matcher.y
gcc -O lex.yy.c -o matcher

现在运行它:

./matcher < input.txt

【讨论】:

我敢打赌,你不会比the awk solution 获得性能改进,因为 awk 针对文本处理进行了高度优化,通常胜过诸如“C”之类的编译语言或该任务。请记住,我们正在寻找字符串,而不是正则表达式。 @EdMorton:对于非正则表达式,基于this on mmaped 文件的 C 版本在大约 1.5 秒内运行我的基准测试......(使用 argv 搜索字符串,而不是一个文件)(将简单的 C 案例扩展到正则表达式并不是那么容易......) 同样,重要的是要搜索 1,000 多个字符串,就像 OP 所说的那样是现实的。搜索 3 个字符串对于比较时间来说真的不是很有用。 @EdMorton Awk 是一个很棒的工具,但它使用了正则表达式解释器。在 5,000 个模式上,它将尝试依次匹配每个输入字符的 5,000 个模式中的每一个。 Flex 会将所有 5000 个模式编译成单个 DFA,每个输入字符执行几条指令。这就是为什么编译器扫描器 - 性能会影响在扫描器生命周期内编译的每个程序 - 是使用 DFA 而不是正则表达式引擎实现的。 在这个问题中我们不使用正则表达式,我们正在使用字符串。没有使用 awks 正则表达式引擎,即使它不会使用单个正则表达式,并且每次找到一个正则表达式时使用的算法都会减少正则表达式的数量。【参考方案10】:
perl -lne '%m = (%m, map $_ => 1 m!\b(string1|string2|string3)\b!g); END  print scalar keys %m == 3 ? "Match": "No Match"' file

【讨论】:

真的是在寻找字符串还是在寻找正则表达式?我的意思是如果 string1 是 ".*" 它会匹配输入中的字符串 ".*" 还是匹配任何字符序列? 它在技术上是正则表达式,但在这种情况下它正在寻找字符串。如果有任何metachars,你需要适当的逃跑。 Perl 没有一些选项可以为你进行转义(比如 shell printf '%q'),所以你的正则表达式有点像字符串一样的处理?我以为我在什么地方看到过。无论如何,为您的正则表达式使用名称“字符串”非常容易产生误导。不过,这似乎是这个线程中的一个共同主题,我知道为什么...... 这里的一个缺点是它总是读取整个文件并且不会在所有字符串匹配时立即停止。【参考方案11】:

假设你要检查的所有字符串都在一个文件strings.txt中,而你要检查的文件是input.txt,下面一行就可以了:

根据 cmets 更新了答案:

$ diff <( sort -u strings.txt )  <( grep -o -f strings.txt input.txt | sort -u )

解释:

使用 grep 的 -o 选项仅匹配您感兴趣的字符串。这会给出文件 input.txt 中存在的所有字符串。然后使用 diff 来获取没有找到的字符串。如果找到所有字符串,结果将是空的。或者,只需检查 diff 的退出代码。

它没有做什么:

找到所有匹配项后立即退出。 可扩展为 regx。 重叠匹配。

它的作用:

查找所有匹配项。 单次调用 grep。 不使用 awk 或 python。

【讨论】:

我们当然可以使用grep -f,而不是使用awk 来创建grep 的表达式,这在我们有很多要搜索的字符串时会更好。这样,处理字符串中的任何特殊字符会更容易。 忽略 UUOC,搜索正则表达式,而不是字符串。您正在使用 awk 创建一个像 regexp1|regexp2|... 这样的正则表达式,它保存在名称错误的变量 STR 中,然后您使用 grep -o 来查找它。 grep -o 必须进行正则表达式匹配,因为您依赖于 STR 中的 |s,因此 STR 的每个部分都被视为正则表达式,您无法解决此问题。它也会以多种方式失败,例如当一个“字符串”是另一个的一部分时,例如试试$ echo 'footherebar' | grep -E -o 'the|there',你会发现输出只是there,而不是thethere。 ` 新版本,可能可以处理纯字符串(通过在grep 中添加-F)(并且没有-F-G,标准正则表达式)(并且可以处理使用带有-E 或其他非标准变体的扩展正则表达式,例如-P 用于GNU grep 中的PCRE 正则表达式) 它仍然无法处理包含在其他字符串中的字符串。再次尝试echo 'footherebar' | grep -E -o 'the|there' 的任何变体,它会找到there,但不会找到the 是的,奇怪的是,如果我的 strings.txt 的内容是 "there\nthe" ,它匹配两个字符串,而如果它是 "the\nthere" 它只匹配一个。 :P。我已经修改了我的答案,说它不能。【参考方案12】:

我没有在答案中看到一个简单的计数器,所以这里有一个使用 awk 的面向计数器的解决方案,一旦满足所有匹配项就会停止:

/string1/  a = 1 
/string2/  b = 1 
/string3/  c = 1 

    if (c + a + b == 3) 
        print "Found!";
        exit;
    

通用脚本

通过 shell 参数扩展使用:

#! /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

用法(可以在其中传递正则表达式):

./script "str1?" "(wo)?men" str3

或应用一串模式:

./script "str1? (wo)?men str3"

【讨论】:

OP 告诉我们她有数千个字符串要搜索,因此像在您的第一个脚本中那样对它们进行硬编码是不切实际的(而且它正在搜索正则表达式,而不是字符串)。您的第二个脚本在使用计数器方面类似于mine,但不是像我的那样减少每个循环上的字符串数组,而是创建一个重复的字符串数组,因此将比我的慢并且使用两倍多记忆。顺便说一句,不是我投反对票。 我在哪里创建重复的字符串数组?而且我不会通过 * 它搜索正则表达式,而不是字符串*,因为无论输入是模式还是文字字符串,它都不会混淆。 @埃德莫顿 在您的第一个脚本中,/string/ 使用内容string 定义了一个repexp 文字,然后将其与$0 进行正则表达式比较,即它正在搜索一个正则表达式而不是字符串。在您的第二个脚本中,您有一个数组args[],其中包含所有字符串,每次匹配输入中的字符串时,您都将其添加到数组temp[],因此当在输入中找到所有字符串时,您最终会temp[]args[] 的副本。 哦,等等 - 我误读了你的第二个脚本。我假设您使用 args[] 将 arg 字符串包含为索引,因此您可以像我正在做的那样对索引进行简单循环和字符串比较,但您不是将字符串存储为数组内容并循环在数字索引上,然后每次取消引用数组并进行正则表达式比较!所以你的第二个脚本中的arg实际上不是一个arg(传入的字符串之一)它是一个索引,而arg/string实际上是args[arg]所以你没有在temp[]中创建一个dup但是有其他问题。 我猜你可能在 if 条件下做了......但让我们谈谈字符串与正则表达式。我将其视为两者的单一解决方案。您单独提供了两个,我不确定它在字符串与正则表达式上的行为效率如何。也许需要一个基准? @埃德莫顿【参考方案13】:

在 python 中使用fileinput module 允许在命令行上指定文件或从标准输入逐行读取文本。您可以将字符串硬编码到 python 列表中。

# Strings to match, must be valid regular expression patterns
# or be escaped when compiled into regex below.
strings = (
    r'string1',
    r'string2',
    r'string3',
)

或从另一个文件中读取字符串

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()

【讨论】:

【参考方案14】:

忽略“是否可以在没有 ... 的情况下执行此操作或使用 awkpython 之类的工具?”要求,您可以使用 Perl 脚本来完成:

(为您的系统使用适当的 shebang 或类似/bin/env perl

#!/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

保存为matcher.pl这将搜索纯文本字符串:

./matcher filename string1 string2 string3 'complex string'

这将搜索正则表达式:

./matcher -r filename regex1 'regex2' 'regex4'

(文件名可以用-f代替):

./matcher -f filename -r string1 string2 string3 'complex string'

仅限于单行匹配模式(由于按行处理文件)。

当从 shell 脚本调用大量文件时,性能比awk 慢(但搜索模式可以包含空格,这与在-vawk 中以空格分隔的模式不同)。如果转换为函数并从 Perl 代码调用(使用包含要搜索的文件列表的文件),它应该比大多数 awk 实现快得多。 (当在几个小文件上调用时,perl 启动时间(脚本的解析等)占主导地位)

无论是否使用正则表达式,硬编码都可以显着加快速度,但代价是灵活性。 (查看我的benchmarks here 了解删除Getopt::Std 有什么效果)

【讨论】:

(假设引用部分中的“使用”应该是“使用”而不是“改为使用”)【参考方案15】:

只是为了“解决方案的完整性”,您可以使用不同的工具并避免多个 grep 和 awk/sed 或大(可能很慢)的 shell 循环;这样的工具是agrep。

agrep实际上是egrep的一种,也支持模式之间的and操作,使用;作为模式分隔符。

egrep 和大多数知名工具一样,agrep 是一种对记录/行进行操作的工具,因此我们仍然需要一种将整个文件视为单个记录的方法。 此外,agrep 提供了一个-d 选项来设置您的自定义记录分隔符。

一些测试:

$ cat file6
str4
str1
str2
str3
str1 str2
str1 str2 str3
str3 str1 str2
str2 str3

$ agrep -d '$$\n' 'str3;str2;str1;str4' file6;echo $?
str4
str1
str2
str3
str1 str2
str1 str2 str3
str3 str1 str2
str2 str3
0

$ agrep -d '$$\n' '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

没有完美的工具,agrep也有一定的局限性;您不能使用超过 32 个字符的正则表达式 /pattern,并且某些选项在与正则表达式一起使用时不可用 - 所有这些都在 agrep man page

中进行了解释

【讨论】:

【参考方案16】:

递归解决方案。一个一个地遍历文件。对于每个文件,检查它是否匹配第一个模式并提前中断(-m1:在第一次匹配时),只有匹配第一个模式,才搜索第二个模式,依此类推:

#!/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

用法:

./allfilter.sh cat filter java
test.sh

在当前目录中搜索标记“cat”、“filter”和“java”。仅在“test.sh”中找到它们。

所以 grep 经常在最坏的情况下被调用(在每个文件的最后一行找到前 N-1 个模式,除了第 N 个模式)。

但是如果可能的话,如果有一个知情的排序(先匹配,早匹配),解决方案应该是合理的快速,因为许多文件被提前放弃,因为它们不匹配第一个关键字,或者被提前接受,因为它们匹配靠近顶部的关键字。

示例:您搜索包含 tailrec(很少使用)、mutable(很少使用,但如果使用,在 import 语句中接近顶部)的 scala 源文件(很少使用,通常不接近顶部)和println (经常使用,不可预知的位置),你会订购它们:

./allfilter.sh mutable tailrec main println 

性能:

ls *.scala | wc 
 89      89    2030

在 89 个 scala 文件中,我有关键字分布:

for keyword in mutable tailrec main println; do grep -m 1 $keyword *.scala | wc -l ; done 
16
34
41
71

使用稍微修改的脚本版本搜索它们,允许使用文件模式作为第一个参数大约需要 0.2 秒:

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

将近 15.000 条代码行:

cat *.scala | wc 
  14913   81614  610893

更新:

在阅读 cmets 的问题后,我们可能正在谈论成千上万的模式,将它们作为论据处理似乎不是一个聪明的主意;更好地从文件中读取它们,并将文件名作为参数传递——也许也可以过滤文件列表:

#!/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 个模式文件):

for i in 1..20
do
   ./allfilter2.sh file.$i.lst pattern.$i.lst > file.$((i+1)).lst
done

【讨论】:

OP 正在寻找一种不需要多次调用 grep(每个文件)的解决方案。 @Leon:这是一个问题,是否有可能,至少对于与以前的模式不匹配的所有文件,不再调用 grep 并且对于每次匹配它都会提前停止,所以如果可能的话,通过一个有根据的排序(先匹配,早匹配),解决方案应该是合理的快速。【参考方案17】:

也许用 gnu sed

cat match_word.sh

sed -z '
  /\b'"$2"'/!bA
  /\b'"$3"'/!bA
  /\b'"$4"'/!bA
  /\b'"$5"'/!bA
  s/.*/0\n/
  q
  :A
  s/.*/1\n/
' "$1"

你这样称呼它:

./match_word.sh infile string1 string2 string3

如果找到所有匹配项,则返回 0,否则返回 1

在这里你可以找到 4 个字符串

如果你想要更多,你可以添加类似的行

/\b'"$x"'/!bA

【讨论】:

该脚本不会查找任何字符串,它会查找 4 个正则表达式。如果您希望它表现得好像正在寻找字符串,请参阅***.com/q/29613304/1745001 了解如何执行此操作。【参考方案18】:

检查文件是否具有所有三种模式的最简单方法是仅获取匹配的模式,仅输出唯一部分和计数行。 然后你就可以用一个简单的测试条件来检查它:test 3 -eq $grep_lines.

 grep_lines=$(grep -Eo 'string1|string2|string3' file | uniq | wc -l)

关于您的第二个问题,我认为一旦发现多个模式就不可能停止读取文件。我已经阅读了 grep 的手册页,但没有任何选项可以帮助您。您只能使用 grep -m [number] 选项在特定行之后停止读取行,无论匹配的模式如何,都会发生这种情况。

很确定为此需要一个自定义函数。

【讨论】:

uniq 仅消除相邻的重复项。这就是我在回答中使用sort -u 的原因。 @Leon 是的,绝对有道理。谢谢你纠正我!【参考方案19】:

你可以

使用grep-o|--only-matching 选项(强制只输出匹配行的匹配部分,每个这样的部分在单独的输出行上),

然后用sort -u消除重复出现的匹配字符串,

最后检查剩余行数是否等于输入字符串数。

演示:

$ cat input 
...
string1
...
string2
...
string3
...
string1 string2
...
string1 string2 string3
...
string3 string1 string2
...
string2 string3
... and so on

$ grep -o -F $'string1\nstring2\nstring3' input|sort -u|wc -l
3

$ grep -o -F $'string1\nstring3' input|sort -u|wc -l
2

$ grep -o -F $'string1\nstring2\nfoo' input|sort -u|wc -l
2

此解决方案的一个缺点(未能满足部分匹配应该没问题要求)是grep 不检测重叠匹配。例如,虽然文本 abcd 匹配 abcbcd,但 grep 只能找到其中一个:

$ grep -o -F $'abc\nbcd' <<< abcd
abc

$ grep -o -F $'bcd\nabc' <<< abcd
abc

请注意,此方法/解决方案仅适用于固定字符串。它不能针对正则表达式进行扩展,因为单个正则表达式可以匹配多个不同的字符串,我们无法跟踪哪个匹配对应于哪个正则表达式。最好的办法是将匹配项存储在一个临时文件中,然后一次使用一个正则表达式运行 grep 多次。


作为 bash 脚本实现的解决方案:

ma​​tchall

#!/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=$'\n'
    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

演示:

$ ./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

【讨论】:

【参考方案20】:

这个gnu-awk 脚本可能会起作用:

cat fileSearch.awk
re == "" 
   exit


   split($0, null, "\\<(" re "\\>)", b)
   for (i=1; i<=length(b); i++)
      gsub("\\<" b[i] "([|]|$)", "", re)

END 
   exit (re != "")

然后将其用作:

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

或者,您可以将此gnu grep 解决方案与PCRE 选项一起使用:

grep -qzP '(?s)(?=.*\bstring1\b)(?=.*\bstring2\b)(?=.*\bstring3\b)' file
使用-z 我们使grep 将完整文件读入单个字符串。 我们正在使用多个前瞻断言来断言所有字符串都存在于文件中。 Regex 必须使用 (?s)DOTALL mod 以使 .* 跨行匹配。

根据man grep

-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.

【讨论】:

No grep 解决方案无论文件中这些字符串的出现顺序如何都有效。前瞻断言只是确保这些字符串存在于文件中的任何位置。所以grep也可以写成:grep -qzP '(?s)(?=.*\bstring3\b)(?=.*\bstring1\b)(?=.*\bstring2\b)' file 确实,您的gnu-awk 解决方案很简洁【参考方案21】:

这是一个有趣的问题,在 grep 手册页中没有任何明显的内容可以提供简单的答案。可能有一个疯狂的正则表达式可以做到这一点,但使用简单的 grep 链可能会更清晰,即使最终扫描文件 n 次。至少 -q 选项让它在每次第一次匹配时退出,如果没有找到其中一个字符串,&& 将进行快捷评估。

$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

【讨论】:

这可行,但是,OP 说他不想多次致电grep 已确认,但我认为没有其他方法可以使用普通的 grep 来实现,至少 -q 和 && 会缩短数据文件的多次传递。

以上是关于检查文件中是不是存在所有多个字符串或正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

检查列表是不是有一个或多个与正则表达式匹配的字符串

检查文件在php中是不是存在正则表达式[重复]

有啥方法可以检查文件是不是存在,文件名包含正则表达式

检查行是不是匹配正则表达式

Pandas Dataframe 使用正则表达式检查值是不是存在

Nginx中的正则如何匹配数字