bash - تحقق مما إذا كانت جميع السلاسل أو regexes المتعددة موجودة في ملف




search grep (14)

أريد التحقق مما إذا كانت جميع السلاسل موجودة في ملف نصي. يمكن أن توجد على نفس الخط أو على خطوط مختلفة. والمطابقات الجزئية يجب أن تكون موافق. مثله:

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

في المثال أعلاه ، يمكن أن يكون لدينا regexes بدلاً من الجمل.

على سبيل المثال ، يتحقق code التالي من وجود أي سلاسل في الملف:

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

كيفية التحقق من وجود كل منهم؟ نظرًا لأننا مهتمون فقط بوجود جميع التطابقات ، يجب أن نتوقف عن قراءة الملف بمجرد مطابقة جميع السلاسل.

هل من الممكن القيام بذلك دون الحاجة إلى استدعاء grep عدة مرات (وهذا لن يتغير عندما يكون حجم ملف الإدخال كبيرًا أو إذا كان لدينا عدد كبير من السلاسل لمطابقته) أو استخدام أداة مثل awk أو python ؟

أيضا ، هل هناك حل للسلاسل التي يمكن تمديدها بسهولة ل recexes؟


git grep

هنا بناء الجملة باستخدام git grep مع أنماط متعددة:

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

يمكنك أيضًا دمج النقوش مع التعبيرات المنطقية مثل --and و --and أو --and أو --and --or .

تحقق man git-grep للحصول على المساعدة.

--all-match عند إعطاء تعبيرات --all-match متعددة ، يتم تحديد هذه العلامة لتقييد التطابق على الملفات التي تحتوي على خطوط لتطابقها جميعًا .

--no-index للبحث عن ملفات في الدليل الحالي لا تتم إدارته بواسطة Git.

-l / --files-with-matches / --name-only عرض أسماء الملفات فقط.

-e المعلمة التالية هي النمط. الافتراضي هو استخدام regexp الأساسية.

العوامل البارزة الأخرى التي يجب مراعاتها:

--threads عدد مؤشرات الترابط عامل grep للاستخدام.

-q / - --quiet / - --silent لا تقم بإخراج الخطوط المتطابقة ؛ الخروج مع الحالة 0 عندما يكون هناك تطابق.

لتغيير نوع النمط ، يمكنك أيضًا استخدام -G / --basic-regexp (افتراضي) ، -F / --fixed-strings ، -E / --extended-regexp ، -P / --perl-regexp ، -f file ، وغيرها.


العديد من هذه الإجابات جيدة بقدر ما تذهب.

ولكن إذا كان الأداء مشكلة - بالتأكيد ممكن إذا كان الإدخال كبيرًا ولديك عدة آلاف من الأنماط - فستحصل على تسريع كبير باستخدام أداة مثل lex أو flex التي تنشئ أوتوماتيكيًا محددًا حقيقيًا محددًا كمعرف بدلاً من الاتصال مترجم regex مرة واحدة لكل نمط.

سوف يقوم التطبيق الآلي المحدود بتنفيذ بعض إرشادات الماكينة لكل حرف إدخال بغض النظر عن عدد الأنماط .

الحل المرن بدون زخرفة:

%{
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

في بيثون باستخدام وحدة fileinput يسمح الملفات المحددة في سطر الأوامر أو نص قراءة سطرا سطرا سطرا من stdin. هل يمكن أن رمز الثابت سلاسل في قائمة بيثون.

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

لسرعة واضحة ، مع عدم وجود قيود أداة خارجية ، ولا يعيد صياغة ، هذه النسخة (الخام) C تقوم بعمل جيد. (ربما Linux فقط ، على الرغم من أنه يجب أن يعمل على جميع أنظمة Unix-like with mmap )

#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

ائتمانات:

ملاحظات:

  • سيتم فحص أجزاء الملف التي تسبق السلاسل المتطابقة عدة مرات - سيفتح الملف مرة واحدة فقط.
  • قد ينتهي تحميل الملف بأكمله في الذاكرة ، خاصةً إذا لم تتطابق السلسلة ، يحتاج نظام التشغيل إلى تحديد ذلك
  • من المحتمل أن تتم إضافة دعم regex باستخدام مكتبة POSIX regex (من المحتمل أن يكون الأداء أفضل قليلاً من grep - يجب أن يستند إلى نفس المكتبة وستحصل على مقدار حمل أقل من فتح الملف مرة واحدة فقط للبحث عن regexes متعددة)
  • يجب أن تعمل الملفات التي تحتوي على فراغات ، ابحث عن السلاسل معهم على الرغم من ...
  • يجب أن تكون جميع الأحرف بخلاف null قابلة للبحث (\ r ، \ n ، إلخ)

متغير 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

Awk هي الأداة التي اخترعها الرجال الذين اخترعوا grep أو shell أو ما إلى ذلك للقيام بمهام التلاعب بالنصوص العامة مثل هذا ، لذا لست متأكدًا من السبب وراء رغبتك في تجنب ذلك.

في حال كانت الإيجاز هو ما تبحث عنه ، إليك GNU awk-one-line لتفعل ما طلبت:

awk 'NR==FNR{a[$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

سيتوقف ما سبق عن قراءة الملف بمجرد مطابقة جميع السلاسل.

إذا كنت تبحث عن regexps بدلاً من الجمل ، فمع GNU awk لـ multi-char RS والاحتفاظ بـ 0 دولار في قسم END ، يمكنك القيام بما يلي:

awk -v RS='^$' 'END{exit !(/regexp1/ && /regexp2/ && /regexp3/)}' file

في الواقع ، حتى لو كانت سلاسل يمكنك القيام بها:

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

المشكلة الرئيسية مع حلول GNU 2 awk المذكورة أعلاه هي أنه ، مثل حل GNU grep -P @ anubhava ، يجب قراءة الملف بالكامل في الذاكرة في وقت واحد ، بينما مع النص awk الأول أعلاه ، سوف يعمل في أي awk في أي قذيفة على أي مربع يونيكس ويخزن فقط سطر واحد من المدخلات في وقت واحد.

أرى أنك أضفت تعليقًا تحت سؤالك للقول إنه يمكن أن يكون لديك عدة آلاف من "الأنماط". على افتراض أنك تعني "سلاسل" ، فبدلاً من تمريرها كوسائط إلى البرنامج النصي ، يمكنك قراءتها من ملف ، على سبيل المثال مع GNU awk لـ multi-char 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

ومن أجل regexps سيكون:

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

وما يعادله regexps:

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

أولاً ، ربما تريد استخدام awk . نظرًا لأنك حذفت هذا الخيار في بيان السؤال ، نعم ، فمن الممكن القيام به وهذا يوفر طريقة للقيام بذلك. من المحتمل أن تكون أبطأ بكثير من استخدام awk ، ولكن إذا كنت ترغب في القيام بذلك على أي حال ...

هذا يعتمد على الافتراضات التالية: G

  • استدعاء AWK أمر غير مقبول
  • استدعاء grep عدة مرات أمر غير مقبول
  • استخدام أي أدوات خارجية أخرى غير مقبول
  • استدعاء grep أقل من مرة أمر مقبول
  • يجب أن يعود النجاح إذا تم العثور على كل شيء ، الفشل عندما لا
  • استخدام bash بدلاً من الأدوات الخارجية أمر مقبول
  • إصدار bash هو> = 3 لإصدار التعبير العادي

قد يلبي هذا جميع متطلباتك: (نسخة regex تفوت بعض التعليقات ، انظر إلى إصدار السلسلة بدلاً من ذلك)

#!/bin/bash

multimatch() {
    filename="$1" # Filename is first parameter
    shift # move it out of the way that "[email protected]" is useful
    strings=( "[email protected]" ) # 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 "[email protected]" is useful
    regexes=( "[email protected]" ) # 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

المعايير

لقد قمت ببعض البحث المعياري .c و .h و .sh في arch / arm / من Linux 4.16.2 للسلاسل "باطلة" و "دالة" و "#define". (تمت إضافة غلافات Shell / الكود الذي تم ضبطه بحيث يمكن testname <filename> <searchstring> [...] باسم testname <filename> <searchstring> [...] ويمكن استخدام علامة if للتحقق من النتيجة)

النتائج: (تقاس مع time ، تقريب الوقت real لأقرب نصف ثانية)

  • multimatch : 49s
  • multimatch_regex : 55s
  • matchall : 10.5s
  • fileMatchesAllNames : 4s
  • awk (الإصدار الأول): 4s
  • موافق: 4.5s
  • بيرل ( إعادة ): 10.5s
  • بيرل غير إعادة : 9.5s
  • Perl non-re optimized : 5s (تمت إزالة Getopt :: Std ودعم regex لبدء التشغيل بشكل أسرع)
  • Perl re optimized : 7s (تمت إزالة Getopt :: Std ودعم غير regex لبدء التشغيل بشكل أسرع)
  • بوابة grep : 3.5s
  • نسخة C (لا ريجكس): 1.5S

(لقد كان استدعاء grep عدة مرات ، خاصة مع الطريقة العودية ، أفضل مما كنت أتوقع)


إنها مشكلة مثيرة للاهتمام ، ولا يوجد شيء واضح في صفحة grep man للإشارة إلى إجابة سهلة. قد يكون هناك ريكس مجنون من شأنه أن يفعل ذلك ، ولكن قد يكون أكثر وضوحا مع سلسلة واضحة من greps ، على الرغم من أن هذا ينتهي مسح الملف مرات 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

حل العودية. تكراره على الملفات واحدا تلو الآخر. لكل ملف ، تحقق مما إذا كان يطابق النموذج الأول وكسر مبكرًا (-m1: في المطابقة الأولى) ، فقط إذا كان يطابق النموذج الأول ، ابحث عن النمط الثاني وما إلى ذلك:

#!/bin/bash

patterns="[email protected]"

fileMatchesAllNames () {
  file=$1
  if [[ $# -eq 1 ]]
  then
    echo "$file"
  else
    shift
    pattern=$1
    shift
    grep -m1 -q "$pattern" "$file" && fileMatchesAllNames "$file" [email protected]
  fi
}

for file in *
do
  test -f "$file" && fileMatchesAllNames "$file" $patterns
done

الاستعمال:

./allfilter.sh cat filter java
test.sh

يبحث في dir الحالي عن الرموز المميزة "cat" و "filter" و "java". العثور عليها فقط في "test.sh".

لذلك يتم استدعاء grep غالبًا في أسوأ السيناريوهات (العثور على أنماط N-1 الأولى في السطر الأخير من كل ملف ، باستثناء نمط N-th).

ولكن مع وجود ترتيب مستنير (نادراً ما يتطابق أولاً مع التطابقات المبكرة أولاً) إذا كان ذلك ممكنًا ، فيجب أن يكون الحل سريعًا بشكل معقول ، حيث يتم التخلي عن العديد من الملفات مبكرًا لأنها لا تتطابق مع الكلمة الرئيسية الأولى ، أو يتم قبولها مبكرًا ، لأنها تتطابق مع إغلاق كلمة رئيسية الى القمة.

مثال: يمكنك البحث في ملف مصدر scala الذي يحتوي على tailrec (نادرًا ما يستخدم) ، وقابل للتغيير (نادرًا ما يستخدم ، ولكن إذا كان الأمر كذلك ، بالقرب من أعلى عبارات الاستيراد) الرئيسي (نادرًا ما يستخدم ، وغالبًا لا قريب من الأعلى) و 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

البحث عنهم باستخدام نسخة معدلة قليلاً من البرامج النصية ، والذي يسمح باستخدام ملف filepatument لأن الحجة الأولى تستغرق حوالي 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

تحديث:

بعد قراءة التعليقات على السؤال ، قد نتحدث عن الآلاف من الأنماط ، ولا يبدو تسليمها كحجج فكرة ذكية ؛ قراءتها بشكل أفضل من ملف ، وتمرير اسم الملف كوسيطة - ربما لقائمة الملفات لتصفية جداً:

#!/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" [email protected]
  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

ربما مع جنو سيد

القط 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

قد يعمل هذا البرنامج النصي 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 قراءة الملف الكامل في سلسلة واحدة.
  • نحن نستخدم تأكيدات lookahead متعددة للتأكيد على أن جميع السلاسل موجودة في الملف.
  • يجب أن يستخدم 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.

يمكنك

  • الاستفادة من -o | --only-matching خيار --only-matching لـ grep (الذي يفرض فقط إخراج الأجزاء المتطابقة من خط المطابقة ، مع كل جزء من هذا القبيل على خط إخراج منفصل) ،

  • ثم القضاء على التكرارات المتكررة للسلاسل المتطابقة مع 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 ) هو أن grep لا يكتشف التطابقات المتداخلة. على سبيل المثال ، على الرغم من أن النص abcd يطابق كل من abc و bcd ، فإن grep يعثر على واحد منهما فقط:

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

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

لاحظ أن هذا النهج / الحل يعمل فقط للسلاسل الثابتة. لا يمكن تمديده لأنواع ريكس ، لأن ريكس واحد يمكن أن يتطابق مع سلاسل مختلفة ولا يمكننا تتبع أي تطابق يتوافق مع أي ريككس. أفضل ما يمكنك فعله هو تخزين التطابقات في ملف مؤقت ، ثم قم بتشغيل grep عدة مرات باستخدام regex واحد في المرة الواحدة.

الحل الذي تم تنفيذه كنص باش:

ماتشال :

#!/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 "[email protected]"|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

$ cat allstringsfile | tr '\n' ' ' |  awk -f awkpattern1

حيث allstringsfile هو ملفك النصي ، كما في السؤال الأصلي. awkpattern1 يحتوي على أنماط السلسلة ، مع && الشرط:

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

perl -lne '%m = (%m, map {$_ => 1} m!\b(string1|string2|string3)\b!g); END { print scalar keys %m == 3 ? "Match": "No Match"}' file




grep