bash - जाँच करें कि क्या सभी फ़ाइल में कई स्ट्रिंग्स या रेग्जेस मौजूद हैं




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 जैसे उपकरण का उपयोग नहीं करेंगे?

इसके अलावा, क्या स्ट्रिंग्स के लिए एक समाधान है जिसे आसानी से रेगेक्स के लिए बढ़ाया जा सकता है?


git grep

यहाँ कई पैटर्न के साथ git grep का उपयोग कर वाक्य रचना है:

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

आप बूलियन अभिव्यक्तियों जैसे --not , --not और --not साथ पैटर्न भी जोड़ सकते हैं।

मदद के लिए man git-grep जाँच करें।

--all-match जब कई पैटर्न एक्सप्रेशन देते हैं, तो यह फ्लैग मैच को उन फाइलों तक सीमित करने के लिए निर्दिष्ट होता है, जिनमें उन सभी को मिलाने के लिए लाइनें होती हैं

--no-index द्वारा प्रबंधित नहीं की गई मौजूदा निर्देशिका में --no-index खोज फाइलें।

-l / --files-with-matches --name-only / --name-only केवल फाइलों के नाम दिखाएं।

-e अगला पैरामीटर पैटर्न है। मूलभूत रीएजएक्सपीपी का उपयोग करना डिफ़ॉल्ट है।

विचार करने के लिए अन्य परम:

--threads grep वर्कर थ्रेड्स की संख्या।

-q / --quiet / --silent मिलान लाइनों का उत्पादन न करें; स्टेटस 0 से बाहर निकलें जब कोई मैच हो।

पैटर्न प्रकार को बदलने के लिए, आप -G / --basic-regexp (डिफ़ॉल्ट), -F / - --fixed-strings , -E / --extended-regexp , -P / - --perl-regexp , का भी उपयोग कर सकते हैं -f file , और अन्य।


इनमें से कई उत्तर ठीक हैं जहाँ तक वे जाते हैं।

लेकिन अगर प्रदर्शन एक मुद्दा है - संभव निश्चित रूप से यदि इनपुट बड़ी है और आप पैटर्न के कई हजारों - तो आप एक मिल जाएगा बड़े speedup की तरह एक उपकरण का उपयोग कर lex या flex कि एक पहचानकर्ता के रूप में एक सच्चे नियतात्मक परिमित automaton बुला के बजाय उत्पन्न करता है प्रति पैटर्न एक बार रेगीक्स दुभाषिया।

परिमित ऑटोमेटन पैटर्न की संख्या की परवाह किए बिना इनपुट चरित्र प्रति कुछ मशीन निर्देशों को निष्पादित करेगा ।

एक नो-फ्रिल्स फ्लेक्स सॉल्यूशन:

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

जांच करने के लिए अपने सभी तार एक फ़ाइल str.txt में मान रहे हैं, और जिस फ़ाइल को आप चेक करना चाहते हैं वह input.txt है, निम्नलिखित एक लाइनर करेगा:

टिप्पणियों के आधार पर उत्तर अपडेट करें:

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

स्पष्टीकरण:

Grep -o विकल्प का उपयोग केवल उन स्ट्रिंग्स से मेल खाने के लिए करें जिनमें आप रुचि रखते हैं। यह उन सभी स्ट्रिंग्स को देता है जो फ़ाइल input.txt में मौजूद हैं। फिर स्ट्रिंग्स का उपयोग करने के लिए पर्याप्त है जो नहीं मिला है। यदि सभी तार पाए गए, तो परिणाम कुछ भी नहीं होगा। या, बस अंतर का निकास कोड जांचें।

यह क्या नहीं करता है:

  • सभी मैच मिलते ही बाहर निकलें।
  • रेक्स करने के लिए विस्तार योग्य।
  • ओवरलैपिंग मैच।

यह क्या करता है:

  • सभी मैच खोजें।
  • Grep को सिंगल कॉल।
  • जाग या अजगर का उपयोग नहीं करता है।

निम्नलिखित 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 ) के लिए एक अलग चेक जोड़ सकते हैं यदि आप सादे (गैर-रेगेक्स) तार के साथ काम कर रहे हैं - तो थोड़ा अधिक कुशल होगा।

क्या इससे आपकी समस्या हल होती है?


सादे गति के लिए, कोई बाहरी उपकरण सीमाओं के साथ, और कोई भी रेगीक्स नहीं, यह (क्रूड) सी संस्करण एक सभ्य काम करता है। (संभवतः केवल लिनक्स, हालांकि इसे सभी यूनिक्स जैसी प्रणालियों पर काम करना चाहिए 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

क्रेडिट:

  • this का उपयोग करता है this
  • फ़ाइल हैंडलिंग ज्यादातर मैन पेज से चुराया गया है mmap

टिप्पणियाँ:

  • यह कई बार मिलान किए गए तार से पहले फ़ाइल के कुछ हिस्सों के माध्यम से स्कैन करेगा - यह केवल एक बार हालांकि फ़ाइल को खोल देगा।
  • पूरी फ़ाइल मेमोरी में लोड हो सकती है, खासकर अगर एक स्ट्रिंग मेल नहीं खाती है, तो ओएस को यह तय करने की आवश्यकता है
  • regex का समर्थन संभवतः POSIX regex लाइब्रेरी का उपयोग करके जोड़ा जा सकता है (प्रदर्शन संभवतः grep की तुलना में थोड़ा बेहतर होगा - यह एक ही लाइब्रेरी पर आधारित होना चाहिए और आप एकाधिक bgexes की खोज के लिए केवल एक बार फ़ाइल खोलने से कम ओवरहेड प्राप्त करेंगे)
  • नल युक्त फाइलें काम करनी चाहिए, हालांकि उनके साथ तार खोजें ...
  • अशक्त के अलावा अन्य सभी अक्षर खोजे जाने योग्य होने चाहिए (\ r, \ n, आदि)

अनदेखा करना "क्या यह बिना संभव है ... या awk या python जैसे उपकरण का उपयोग करना संभव है?" आवश्यकता, आप इसे एक पर्ल स्क्रिप्ट के साथ कर सकते हैं:

(अपने सिस्टम के लिए एक उपयुक्त शेबंग का उपयोग करें या जैसे कुछ /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.pl यह सादे पाठ तार के लिए खोज करेगा:

./matcher filename string1 string2 string3 'complex string'

यह नियमित अभिव्यक्ति की खोज करेगा:

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

(फ़ाइल नाम इसके बजाय -f साथ दिया जा सकता है):

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

यह सिंगल लाइन मैचिंग पैटर्न (फाइल लाइनवेज से निपटने के कारण) तक सीमित है।

शेल स्क्रिप्ट से बहुत सारी फ़ाइलों के लिए कॉल करते समय प्रदर्शन, awk की तुलना में धीमा होता है (लेकिन खोज पैटर्न में रिक्त स्थान शामिल हो सकते हैं, जो कि गुजरे हुए अंतरिक्ष-पृथक -v से awk विपरीत हैं) यदि किसी फ़ंक्शन में कनवर्ट किया जाता है और पर्ल कोड से कॉल किया जाता है (खोज के लिए फ़ाइलों की सूची वाली फ़ाइल के साथ), तो यह अधिकांश awk कार्यान्वयनों की तुलना में बहुत तेज़ होना चाहिए। (जब कई स्मालिश फाइल्स पर कॉल किया जाता है, तो पर्ल स्टार्टअप टाइम (स्क्रिप्टिंग आदि) टाइमिंग पर हावी हो जाता है)

लचीलेपन की कीमत पर नियमित रूप से अभिव्यक्ति का उपयोग किया जाता है या नहीं, इसे हार्डकोडिंग द्वारा काफी बढ़ाया जा सकता है। ( Getopt::Std has को हटाने के प्रभाव को देखने के लिए यहां मेरे बेंचमार्क देखें)


आप ऐसा कर सकते हैं

  • -o का उपयोग करें | --only-matching grep का --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 abc और bcd दोनों से मेल खाता है, लेकिन grep उनमें से केवल एक को ढूँढता है:

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

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

ध्यान दें कि यह दृष्टिकोण / समाधान केवल निश्चित तारों के लिए काम करता है। इसे रेगेक्स के लिए नहीं बढ़ाया जा सकता है, क्योंकि एक एकल रेक्सक्स कई अलग-अलग तारों से मेल खा सकता है और हम यह पता नहीं लगा सकते हैं कि कौन सा मैच किस रेगेक्स से मेल खाता है। आप जो सबसे अच्छा कर सकते हैं वह मैचों को एक अस्थायी फ़ाइल में संग्रहीत करता है, और फिर एक बार में एक regex का उपयोग करके कई बार grep चलाते हैं।

समाधान को बैश स्क्रिप्ट के रूप में लागू किया गया:

चटाई :

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

एक पुनरावर्ती समाधान। एक-एक करके फाइलों पर नजर डालें। प्रत्येक फ़ाइल के लिए, जांचें कि क्या यह पहले पैटर्न से मेल खाता है और जल्दी टूट जाता है (-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

टोकन "बिल्ली", "फिल्टर" और "जावा" के लिए वर्तमान डायर में खोज करता है। उन्हें केवल "test.sh" में मिला।

इसलिए grep को अक्सर सबसे खराब स्थिति में लागू किया जाता है (N-th पैटर्न को छोड़कर, प्रत्येक फ़ाइल की अंतिम पंक्ति में पहला N-1 पैटर्न ढूंढना)।

लेकिन यदि संभव हो तो एक सूचित आदेश (दुर्लभ मिलान पहले, शुरुआती मैचिंग) के साथ, समाधान उचित तेजी से होना चाहिए, क्योंकि कई फाइलें जल्दी छोड़ दी जाती हैं क्योंकि वे पहले कीवर्ड से मेल नहीं खाते थे, या जल्दी स्वीकार किए जाते हैं, क्योंकि वे एक कीवर्ड के साथ मेल खाते थे। सबसे ऊपर।

उदाहरण: आप एक स्कैला स्रोत फ़ाइल खोजते हैं, जिसमें टेलरेक (कुछ हद तक शायद ही कभी इस्तेमाल किया जाता है), म्यूटेबल (शायद ही कभी इस्तेमाल किया जाता है, लेकिन यदि ऐसा है, तो आयात विवरणों में शीर्ष के करीब) मुख्य (शायद ही कभी इस्तेमाल किया जाता है, अक्सर शीर्ष के करीब नहीं) और प्रिंटलाइन (अक्सर इस्तेमाल किया, अप्रत्याशित स्थिति), आप उन्हें आदेश देंगे:

./allfilter.sh mutable tailrec main println 

प्रदर्शन:

ls *.scala | wc 
 89      89    2030

89 स्कैला फ़ाइलों में, मेरे पास कीवर्ड वितरण है:

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

अद्यतन करें:

इस प्रश्न के लिए टिप्पणियों में पढ़ने के बाद, कि हम पैटर्न के बारे में बात कर रहे हैं, उन्हें तर्क के रूप में सौंपना एक चतुर विचार नहीं लगता है; बेहतर उन्हें एक फ़ाइल से पढ़ें, और फ़ाइल नाम को तर्क के रूप में पास करें - शायद फ़िल्टर करने के लिए फ़ाइलों की सूची के लिए:

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

मेरे लिए यह जांचने का सबसे आसान तरीका है कि फ़ाइल में सभी तीन पैटर्न हैं, केवल मिलान किए गए पैटर्न प्राप्त करने के लिए, केवल अनन्य भागों और गणना लाइनों को आउटपुट करें। फिर आप इसे एक साधारण टेस्ट कंडीशन के साथ देख पाएंगे: test 3 -eq $grep_lines

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

आपके दूसरे प्रश्न के बारे में, मुझे नहीं लगता कि एक से अधिक पैटर्न मिलते ही फाइल को पढ़ना बंद करना संभव है। मैंने grep के लिए मैन पेज पढ़ा है और ऐसा कोई विकल्प नहीं है जो आपकी मदद कर सके। आप केवल एक विकल्प grep -m [number] साथ विशिष्ट एक के बाद पठन रेखाओं को रोक सकते हैं, जो मिलान किए गए पैटर्न की कोई बात नहीं होती है।

बहुत यकीन है कि उस उद्देश्य के लिए एक कस्टम फ़ंक्शन की आवश्यकता है।


यह 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 पूरी फाइल को एक स्ट्रिंग में पढ़ते हैं।
  • हम कई लुकहैड कथनों का उपयोग इस बात के लिए कर रहे हैं कि फाइल में सभी तार मौजूद हैं।
  • रेगेक्स को बनाने के लिए (?s) या DOTALL मॉड का उपयोग करना चाहिए।

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.

शायद गानू सेड के साथ

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

सबसे पहले, आप शायद awk का उपयोग करना चाहते हैं। चूंकि आपने प्रश्न कथन में उस विकल्प को समाप्त कर दिया है, हां, यह करना संभव है और यह इसे करने का एक तरीका प्रदान करता है। यह awk का उपयोग करने की तुलना में बहुत धीमी है, लेकिन अगर आप इसे वैसे भी करना चाहते हैं ...

यह निम्नलिखित मान्यताओं पर आधारित है: जी

  • 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 in arch / arm / Linux से 4.16.2 के लिए स्ट्रिंग्स "void", "function", और "#define" की। (शेल रैपर जोड़े गए / कोड को ट्यून किया गया कि सभी को testname <filename> <searchstring> [...] रूप में कहा जा सकता है और if परिणाम को जांचने के लिए उपयोग किया जा सकता है)

परिणाम: ( time साथ मापा जाता है, real समय निकटतम आधा सेकंड तक)

  • multimatch : 49 एस
  • multimatch_regex : 55s
  • matchall : 10.5s
  • fileMatchesAllNames : 4 जी
  • awk (पहला संस्करण): 4s
  • agrep: 4.5 से
  • पर्ल री (-आर): 10.5 एस
  • पर्ल नॉन-री : 9.5 s
  • पर्ल नॉन-री ऑप्टिमाइज़्ड : 5 एस (रिमूव गेटअप :: तेज स्टार्टअप के लिए एसटीडी और रेगेक्स सपोर्ट)
  • पर्ल री ऑप्टिमाइज़्ड : 7s (रिमूव गेटअप :: तेज स्टार्टअप के लिए एसटीडी और नॉन-रेगेक्स सपोर्ट)
  • git grep : 3.5 से
  • सी संस्करण (कोई रेगेक्स): 1.5 एस

(कई बार grep को आमंत्रित करना, विशेष रूप से पुनरावर्ती विधि के साथ, मैंने अपेक्षा से बेहतर किया)


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

जहां ऑलस्ट्रिंगफाइल आपकी टेक्स्ट फाइल है, जैसा कि मूल प्रश्न में है। 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