sustituir regulares reemplazar linea insertar expresiones especiales eliminar comando caracteres caracter bash search grep

bash - regulares - sed linux reemplazar



Compruebe si existen todas las cadenas múltiples o expresiones regulares en un archivo (20)

git grep

Aquí está la sintaxis usando git grep con múltiples patrones:

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

También puede combinar patrones con expresiones booleanas como --and , --or y --not .

Consulte man git-grep para obtener ayuda.

--all-match Al dar múltiples expresiones de patrón, esta bandera se especifica para limitar la coincidencia a los archivos que tienen líneas para que coincidan con todos ellos .

--no-index Busca archivos en el directorio actual que Git no administra.

-l / --files-with-matches / --name-only Muestra solo los nombres de los archivos.

-e El siguiente parámetro es el patrón. El valor predeterminado es usar regexp básico.

Otros parámetros a considerar:

--threads Número de hilos de trabajo grep para usar.

-q / --quiet / --silent No --silent líneas coincidentes; salir con el estado 0 cuando hay una coincidencia.

Para cambiar el tipo de patrón, también puede usar -G / --basic-regexp (predeterminado), -F / --fixed-strings , -E / --extended-regexp , -P / --perl-regexp , -f file , y otros.

Quiero verificar si todas mis cadenas existen en un archivo de texto. Podrían existir en la misma línea o en diferentes líneas. Y las coincidencias parciales deberían estar bien. Me gusta esto:

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

En el ejemplo anterior, podríamos tener expresiones regulares en lugar de cadenas.

Por ejemplo, el siguiente code verifica si alguna de mis cadenas existe en el archivo:

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

¿Cómo verificar si todos existen? Como solo estamos interesados ​​en la presencia de todas las coincidencias, deberíamos dejar de leer el archivo tan pronto como todas las cadenas coincidan.

¿Es posible hacerlo sin tener que invocar grep varias veces (que no se escalará cuando el archivo de entrada es grande o si tenemos una gran cantidad de cadenas para que coincida) o usar una herramienta como awk o python ?

Además, ¿hay una solución para las cadenas que se pueden extender fácilmente para expresiones regulares?


El siguiente python script debería hacer el truco. De alguna manera llama al equivalente de grep ( re.search ) varias veces para cada línea, es decir, busca cada patrón para cada línea, pero como no está bifurcando un proceso cada vez, debería ser mucho más eficiente. Además, elimina los patrones que ya se han encontrado y se detiene cuando se han encontrado todos.

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

Puede agregar una verificación por separado para cadenas simples ( string in line ) si se trata de cadenas simples (no regex), será un poco más eficiente.

¿Eso resuelve tu problema?


En python, el uso del módulo fileinput permite que los archivos se especifiquen en la línea de comando o en el texto leído línea por línea desde stdin. Podría codificar las cadenas en una lista de 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'', )

o leer las cadenas de otro archivo

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


Muchas de estas respuestas están bien hasta donde llegan.

Pero si el rendimiento es un problema, ciertamente posible si la entrada es grande y tiene muchos miles de patrones, entonces obtendrá una gran aceleración utilizando una herramienta como lex o flex que genera un verdadero autómata finito determinista como un reconocedor en lugar de llamar un intérprete de expresiones regulares una vez por patrón.

El autómata finito ejecutará algunas instrucciones de máquina por carácter de entrada, independientemente del número de patrones .

Una solución flexible sin lujos:

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

Una desventaja es que tendrías que construir esto para cada conjunto de patrones. Eso no es tan malo:

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

Ahora ejecútelo:

./matcher < input.txt


No vi un contador simple entre las respuestas, así que aquí hay una solución orientada al contador awk que se detiene tan pronto como se satisfacen todas las coincidencias:

/string1/ { a = 1 } /string2/ { b = 1 } /string3/ { c = 1 } { if (c + a + b == 3) { print "Found!"; exit; } }

Un script genérico

para expandir el uso a través de argumentos de 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

Uso (en el que puede pasar expresiones regulares):

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

o para aplicar una cadena de patrones:

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


Para una velocidad simple, sin limitaciones de herramientas externas y sin expresiones regulares, esta versión (cruda) C hace un trabajo decente. (Posiblemente solo Linux, aunque debería funcionar en todos los sistemas tipo Unix con 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); }

Compilarlo con:

gcc matcher.c -o matcher

Ejecútalo con:

./matcher filename needle1 needle2 needle3

Créditos:

  • utiliza this
  • Manejo de archivos mayormente robado de la mmap página man

Notas:

  • Escaneará las partes del archivo que preceden a las cadenas coincidentes varias veces; sin embargo, solo abrirá el archivo una vez.
  • El archivo completo podría terminar cargado en la memoria, especialmente si una cadena no coincide, el sistema operativo debe decidir que
  • El soporte de expresiones regulares probablemente se puede agregar mediante el uso de la biblioteca de expresiones regulares POSIX (el rendimiento probablemente sea un poco mejor que grep; se debe basar en la misma biblioteca y obtendría una sobrecarga reducida al abrir el archivo una sola vez para buscar múltiples expresiones regulares)
  • Los archivos que contienen valores nulos deberían funcionar, aunque las cadenas de búsqueda con ellos no ...
  • Todos los caracteres que no sean nulos deben poder buscarse (/ r, / n, etc.)

Suponiendo que todas sus cadenas para verificar están en un archivo strings.txt, y el archivo que desea registrar es input.txt, lo siguiente hará:

Actualización de la respuesta basada en comentarios:

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

Explicacion:

Use la opción grep''s -o para hacer coincidir solo las cadenas que le interesan. Esto le da todas las cadenas que están presentes en el archivo input.txt. Luego use diff para obtener las cadenas que no se encuentran. Si se encontraran todas las cadenas, el resultado sería nada. O simplemente verifique el código de salida de diff.

Lo que no hace:

  • Salga tan pronto como se encuentren todas las coincidencias.
  • Extensible a regx.
  • Partidos superpuestos.

Lo que hace:

  • Encuentra todos los partidos.
  • Llamada única a grep.
  • No utiliza awk ni python.

Una variante más de Perl: cada vez que todas las cadenas coinciden ... incluso cuando el archivo se lee a la mitad, el procesamiento se completa y solo imprime los resultados

> 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 es la herramienta que los chicos que inventaron grep, shell, etc., inventaron para realizar trabajos generales de manipulación de texto como este, así que no estoy seguro de por qué querrías evitarlo.

En caso de que la brevedad sea lo que está buscando, aquí está el GNU awk one-liner para hacer exactamente lo que pidió:

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

Y aquí hay un montón de otra información y opciones:

Asumiendo que realmente estás buscando cadenas, sería:

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

lo anterior dejará de leer el archivo tan pronto como todas las cadenas coincidan.

Si estaba buscando expresiones regulares en lugar de cadenas, entonces con GNU awk para RS de múltiples caracteres y retención de $ 0 en la sección FINAL, podría hacer:

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

En realidad, incluso si se tratara de cadenas, podría hacer:

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

El problema principal con las 2 soluciones anteriores de GNU awk es que, al igual que la solución GNU grep -P de @anubhava, todo el archivo debe leerse en la memoria al mismo tiempo, mientras que con el primer script awk anterior, funcionará en cualquier awk en cualquier shell en cualquier cuadro de UNIX y solo almacena una línea de entrada a la vez.

Veo que ha agregado un comentario debajo de su pregunta para decir que podría tener varios miles de "patrones". Suponiendo que quiere decir "cadenas", en lugar de pasarlas como argumentos al script, puede leerlas desde un archivo, por ejemplo, con GNU awk para RS de múltiples caracteres y un archivo con una cadena de búsqueda por línea:

awk '' NR==FNR { strings[$0]; next } { for (string in strings) if ( !index($0,string) ) exit 1 } '' file_of_strings RS=''^$'' file_to_be_searched

y para regexps sería:

awk '' NR==FNR { regexps[$0]; next } { for (regexp in regexps) if ( $0 !~ regexp ) exit 1 } '' file_of_regexps RS=''^$'' file_to_be_searched

Si no tiene GNU awk y su archivo de entrada no contiene caracteres NUL, puede obtener el mismo efecto que el anterior utilizando RS=''/0'' lugar de RS=''^$'' o agregando a la variable una línea en un tiempo a medida que se lee y luego procesa esa variable en la sección FIN.

Si su file_to_be_searched es demasiado grande para caber en la memoria, sería esto para cadenas:

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

y el equivalente para 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


Es un problema interesante, y no hay nada obvio en la página de manual de grep que sugiera una respuesta fácil. Puede haber una expresión regular demente que lo haga, pero puede ser más clara con una cadena directa de greps, aunque eso termine escaneando el archivo n veces. Al menos la opción -q tiene fianza en el primer partido cada vez, y el && atajará la evaluación si no se encuentra una de las cadenas.

$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


Este script gnu-awk puede funcionar:

cat fileSearch.awk re == "" { exit } { split($0, null, "//<(" re "//>)", b) for (i=1; i<=length(b); i++) gsub("//<" b[i] "([|]|$)", "", re) } END { exit (re != "") }

Luego úsalo como:

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

Alternativamente , puede usar esta solución gnu grep con la opción PCRE :

grep -qzP ''(?s)(?=.*/bstring1/b)(?=.*/bstring2/b)(?=.*/bstring3/b)'' file

  • Usando -z hacemos que grep lea el archivo completo en una sola cadena.
  • Estamos utilizando múltiples aserciones anticipadas para afirmar que todas las cadenas están presentes en el archivo.
  • Regex debe usar (?s) o DOTALL mod para hacer que .* coincida entre líneas.

Según el 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.


Ignorando el "¿Es posible hacerlo sin ... o usar una herramienta como awk o python ?" requisito, puede hacerlo con un script Perl:

(Use un shebang apropiado para su sistema o algo como /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

Se guarda como matcher.pl esto buscará cadenas de texto sin formato:

./matcher filename string1 string2 string3 ''complex string''

Esto buscará expresiones regulares:

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

(El nombre de archivo se puede dar con -f lugar):

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

Está limitado a patrones de coincidencia de una sola línea (debido a que se trata el archivo en línea).

El rendimiento, cuando se requieren muchos archivos de un script de shell, es más lento que awk (pero los patrones de búsqueda pueden contener espacios, a diferencia de los que se pasan separados por espacios en -v a awk ). Si se convierte a una función y se llama desde el código Perl (con un archivo que contiene una lista de archivos para buscar), debería ser mucho más rápido que la mayoría de las implementaciones de awk . (Cuando se invoca en varios archivos pequeños, el tiempo de inicio de perl (análisis, etc. del script) domina el tiempo)

Se puede acelerar significativamente codificando si se usan o no expresiones regulares, a costa de la flexibilidad. (Vea mis puntos de referencia aquí para ver qué efecto tiene la eliminación de Getopt::Std )


La forma más fácil para mí de verificar si el archivo tiene los tres patrones es obtener solo patrones coincidentes, generar solo partes únicas y líneas de conteo. Luego podrá verificarlo con una simple condición de test 3 -eq $grep_lines : test 3 -eq $grep_lines .

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

Con respecto a su segunda pregunta , no creo que sea posible dejar de leer el archivo tan pronto como se encuentre más de un patrón. He leído la página de manual de grep y no hay opciones que puedan ayudarte con eso. Solo puede dejar de leer líneas después de una específica con una opción grep -m [number] que ocurre sin importar los patrones coincidentes.

Bastante seguro de que se necesita una función personalizada para ese propósito.


Primero, probablemente quieras usar awk . Como eliminó esa opción en el enunciado de la pregunta, sí, es posible hacerlo y esto proporciona una forma de hacerlo. Es probable que sea MUCHO más lento que usar awk , pero si quieres hacerlo de todos modos ...

Esto se basa en los siguientes supuestos: G

  • Invocar AWK es inaceptable
  • Invocar grep varias veces es inaceptable
  • El uso de cualquier otra herramienta externa es inaceptable
  • Invocar grep menos de una vez es aceptable
  • Debe devolver el éxito si se encuentra todo, el fracaso cuando no
  • Usar bash lugar de herramientas externas es aceptable
  • bash versión bash es> = 3 para la versión de expresión regular

Esto podría cumplir con todos sus requisitos: (la versión de regex pierde algunos comentarios, mire la versión de cadena en su lugar)

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

Puntos de referencia

Hice algunos benchmarking buscando .c , .h y .sh en arch / arm / desde Linux 4.16.2 para las cadenas "void", "function" y "#define". (Se agregaron envoltorios de shell / se ajustó el código para que todos puedan llamarse como testname <filename> <searchstring> [...] y que se pueda usar un if para verificar el resultado)

Resultados: (medido con el time , tiempo real redondeado al medio segundo más cercano)

  • multimatch : 49s
  • multimatch_regex : 55s
  • matchall : 10.5s
  • fileMatchesAllNames : 4s
  • awk (primera versión): 4s
  • agrep: 4.5s
  • Perl re (-r): 10.5s
  • Perl no re : 9.5s
  • Perl no re-optimizado : 5s (Getopt eliminado :: Std y soporte de expresiones regulares para un inicio más rápido)
  • Perl re optimized : 7s (Getopt eliminado :: Std y soporte no regex para un inicio más rápido)
  • git grep : 3.5s
  • Versión C (sin expresiones regulares): 1.5s

(Invocar grep varias veces, especialmente con el método recursivo, funcionó mejor de lo que esperaba)


Quizás con 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"

y lo llamas así:

./match_word.sh infile string1 string2 string3

devuelve 0 si se encuentran todas las coincidencias más 1

aquí puedes buscar 4 cuerdas

si quieres más, puedes agregar líneas como

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


Solo para "soluciones completas", puede usar una herramienta diferente y evitar múltiples greps y awk / sed o bucles de shell grandes (y probablemente lentos); Tal herramienta es agrep .

agrep es en realidad una especie de soporte egrep también and operación entre patrones, usando ; como un separador de patrones

Al igual que egrep y la mayoría de las herramientas conocidas, agrep es una herramienta que funciona en registros / líneas y, por lo tanto, todavía necesitamos una forma de tratar todo el archivo como un único registro.
Además agrep proporciona una opción -d para establecer su delimitador de registro personalizado.

Algunas pruebas:

$ 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

Ninguna herramienta es perfecta y agrep también tiene algunas limitaciones; no puede usar una expresión regular / patrón de más de 32 caracteres y algunas opciones no están disponibles cuando se usa con expresiones regulares, todo esto se explica en la agrep


Una solución recursiva. Iterar sobre los archivos uno por uno. Para cada archivo, verifique si coincide con el primer patrón y rompa temprano (-m1: en la primera coincidencia), solo si coincide con el primer patrón, busque el segundo patrón y así sucesivamente:

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

Uso:

./allfilter.sh cat filter java test.sh

Busca en el directorio actual los tokens "cat", "filter" y "java". Los encontré solo en "test.sh".

Entonces grep se invoca a menudo en el peor de los casos (encontrar los primeros patrones N-1 en la última línea de cada archivo, excepto el patrón N-ésimo).

Pero con un pedido informado (coincide poco antes, primero coincide primero) si es posible, la solución debe ser razonablemente rápida, ya que muchos archivos se abandonan temprano porque no coinciden con la primera palabra clave, o se aceptan temprano, ya que coinciden con el cierre de una palabra clave a la cima.

Ejemplo: Usted busca un archivo fuente scala que contiene tailrec (algo raramente usado), mutable (raramente usado, pero si es así, cerca de la parte superior en las declaraciones de importación) main (raramente usado, a menudo no cerca de la parte superior) e println (a menudo posición impredecible), los ordenaría:

./allfilter.sh mutable tailrec main println

Actuación:

ls *.scala | wc 89 89 2030

En 89 archivos scala, tengo la distribución de palabras clave:

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

Al buscarlos con una versión ligeramente modificada de los scripts, lo que permite usar un patrón de archivos como primer argumento toma alrededor de 0.2s:

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

en cerca de 15,000 líneas de código:

cat *.scala | wc 14913 81614 610893

actualizar:

Después de leer en los comentarios a la pregunta, que podríamos estar hablando de miles de patrones, entregarlos como argumentos no parece ser una idea inteligente; mejor léalos de un archivo y pase el nombre del archivo como argumento, tal vez para que la lista de archivos también se filtre:

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

Si el número y la longitud de los patrones / archivos exceden las posibilidades de pasar argumentos, la lista de patrones podría dividirse en muchos archivos de patrones y procesarse en un bucle (por ejemplo, de 20 archivos de patrones):

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


Usted puede

  • hacer uso de la -o | --only-matching opción de --only-matching de grep (que obliga a generar solo las partes coincidentes de una línea coincidente, con cada parte en una línea de salida separada),

  • luego elimine las ocurrencias duplicadas de cadenas coincidentes con sort -u ,

  • y finalmente verifique que el recuento de las líneas restantes sea igual al recuento de las cadenas de entrada.

Demostración:

$ 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

Una deficiencia con esta solución (no cumplir con las coincidencias parciales debería ser un requisito correcto ) es que grep no detecta coincidencias superpuestas. Por ejemplo, aunque el texto abcd coincide con abc y bcd , grep encuentra solo uno de ellos:

$ grep -o -F $''abc/nbcd'' <<< abcd abc $ grep -o -F $''bcd/nabc'' <<< abcd abc

Tenga en cuenta que este enfoque / solución solo funciona para cadenas fijas. No se puede extender para expresiones regulares, porque una sola expresión regular puede coincidir con varias cadenas diferentes y no podemos rastrear qué coincidencia corresponde a qué expresión regular. Lo mejor que puede hacer es almacenar las coincidencias en un archivo temporal y luego ejecutar grep varias veces utilizando una expresión regular a la vez.

La solución implementada como un script bash:

matchall :

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

Demostración:

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

Donde allstringsfile es su archivo de texto, como en la pregunta original. awkpattern1 contiene los patrones de cadena, con && condición:

$ 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